Riaan's SysAdmin Blog

My tips, howtos, gotchas, snippets and stuff. Use at your own risk!

PHP

Amazon SES Submission

I have experimented using the Amazon API to submit email to SES. As opposed to using SMTP the API may provide some advantages in some situations.

I jotted down some notes how I used the API and CommandPool since it took me a while to get it to work.

Out of scope:
1. SES and DKIM setup.
2. Requesting/Upping 1 email/sec SES rate limits.
3. SES sandbox and TO/FROM approvals.
4. Environment setup ie PHP 7/Nginx.
5. KEY/SECRET obfuscation from script ie store in hidden home folder.
6. API v1 and v2 versus v3 changes.

Input file so I can pass to CURL for testing.

$ cat emails.inp 
[
{"email":"success@simulator.amazonses.com"},{"email":"success@simulator.amazonses.com"},{"email":"success@simulator.amazonses.com"},{"email":"success@simulator.amazonses.com"},{"email":"success@simulator.amazonses.com"},{"email":"success@simulator.amazonses.com"},{"email":"success@simulator.amazonses.com"},{"email":"success@simulator.amazonses.com"},{"email":"success@simulator.amazonses.com"},{"email":"success@simulator.amazonses.com"}
]

Run it from curl.

$ curl -H "Content-Type: application/json" -X POST --data-binary @emails.inp http://myserver.com/ses/amazon-commandpool_v2.php
About to send item 0 to: success@simulator.amazonses.com
About to send item 1 to: success@simulator.amazonses.com
About to send item 2 to: success@simulator.amazonses.com
About to send item 3 to: success@simulator.amazonses.com
About to send item 4 to: success@simulator.amazonses.com
Completed 0: 01000157c88196ba-e4a8d71a-c2a3-4d58-951a-e43639f29e05-000000 and result was: 200 
About to send item 5 to: success@simulator.amazonses.com
Completed 2: 01000157c88196b8-4b4bb9c4-a5af-4cd3-ad0c-808817910d12-000000 and result was: 200 
About to send item 6 to: success@simulator.amazonses.com
Completed 1: 01000157c88196b9-33fb8303-f7b5-451e-acd3-619b239053e6-000000 and result was: 200 
About to send item 7 to: success@simulator.amazonses.com
Completed 3: 01000157c88196b6-9a5d8224-441a-4f12-8b80-137716e7e11c-000000 and result was: 200 
About to send item 8 to: success@simulator.amazonses.com
Completed 4: 01000157c88196ba-c8cebab9-4397-490c-b425-10dcda4b99d4-000000 and result was: 200 
About to send item 9 to: success@simulator.amazonses.com
Completed 5: 01000157c881972b-3e243162-1eb4-4ad3-8c39-16ce31e10144-000000 and result was: 200 
Completed 8: 01000157c881975b-64270cf4-9666-408b-b16f-510a6cb4ff70-000000 and result was: 200 
Completed 7: 01000157c881974d-a2f67bfb-6200-44be-9164-16d099ca6dcf-000000 and result was: 200 
Completed 6: 01000157c8819734-8b3c6347-4a10-44b3-bd59-4e75fc7a2e9f-000000 and result was: 200 
Completed 9: 01000157c88197bc-574976c4-22e0-41ef-a5b6-1e810b666b58-000000 and result was: 200 

Total Execution Time: 0.44663500785828 Sec(s)

Script to accept emails in JSON and use the CommandPool API for asynchronous promises.

# cat amazon-commandpool_v2.php
<?php $time_start = microtime(true); if(strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') != 0){ //throw new Exception('Request method must be POST!'); exit('Request method must be POST!'); } //Make sure that the content type of the POST request has been set to application/json $contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : ''; if(strcasecmp($contentType, 'application/json') != 0){ //throw new Exception('Content type must be: application/json'); exit('Content type must be: application/json'); } //Receive the RAW post data. $content = trim(file_get_contents("php://input")); //Attempt to decode the incoming RAW post data from JSON. $decoded = json_decode($content, true); //If json_decode failed, the JSON is invalid. if(!is_array($decoded)){ //throw new Exception('Received content contained invalid JSON!'); exit('Received content contained invalid JSON!'); } require '/sites1/myserver.com/web/ses/aws-autoloader.php'; use Aws\Exception\AwsException; use Aws\Ses\SesClient; use Aws\CommandPool; use Aws\CommandInterface; use Aws\ResultInterface; use GuzzleHttp\Promise\PromiseInterface; define('REGION','us-east-1'); $client = SesClient::factory([ 'version'=> 'latest',
    'region' => REGION,
    'credentials' => [
      'key'    => 'MYKEY',
      'secret' => 'MYSECRET',
    ]
]);

define('SENDER', 'myfromaddress@mydomain.com');
//define('RECIPIENT', array('success@simulator.amazonses.com','success@simulator.amazonses.com'));
//define('RECIPIENT', array('success@simulator.amazonses.com'));
define('SUBJECT','Amazon SES test (AWS SDK for PHP)');
define('BODY','This email was sent with Amazon SES using the AWS SDK for PHP.');

//$addresses = RECIPIENT;
$addresses = array_column($decoded, 'email');

$commandGenerator = function ($addresses) use ($client) {
    foreach ($addresses as $address) {
        // Yield a command that will be executed by the pool.
	$request = array();
	$request['Source'] = SENDER;
	$request['Destination']['ToAddresses'] = array($address);
	$request['Message']['Subject']['Data'] = SUBJECT;
	$request['Message']['Body']['Text']['Data'] = BODY;
        yield $client->getCommand('SendEmail', 
            $request
        );
    }
};

$commands = $commandGenerator($addresses);

$pool = new CommandPool($client, $commands, [
  'concurrency' => 5,
  'before' => function (CommandInterface $cmd, $iterKey) {
	$a = $cmd->toArray();
        echo "About to send item {$iterKey} to: " 
            . $a['Destination']['ToAddresses'][0] . "\n";
            //. print_r($cmd->toArray(), true) . "\n";
    },
  'fulfilled' => function (
        ResultInterface $result,
        $iterKey,
        PromiseInterface $aggregatePromise
    ) {
        echo "Completed {$iterKey}: {$result['MessageId']} and result was: {$result['@metadata']['statusCode']} \n";
        //echo "Completed {$iterKey}: {$result}\n";
    },
  'rejected' => function (
        AwsException $reason,
        $iterKey,
        PromiseInterface $aggregatePromise
    ) {
        echo "Failed {$iterKey}: {$reason}\n";
    },

]);

// Initiate the pool transfers
$promise = $pool->promise();

// Force the pool to complete synchronously
$promise->wait();

// Or you can chain then calls off of the pool
//$promise->then(function() { echo "Done\n" };

$time_end = microtime(true);
//dividing with 60 will give the execution time in minutes other wise seconds
//$execution_time = ($time_end - $time_start)/60;
$execution_time = ($time_end - $time_start);
//execution time of the script
echo "\nTotal Execution Time: ".$execution_time." Sec(s)\n";

Test from a web page as opposed to CURL command line.

# cat amazon-commandpool_v2_test.php
<?php //API Url $url = 'http://myserver.com/ses/amazon-commandpool_v2.php'; //Initiate cURL. $ch = curl_init($url); //The JSON data. $jsonData = array( array('email' => 'success@simulator.amazonses.com'),
    array('email' => 'success@simulator.amazonses.com'),
    array('email' => 'success@simulator.amazonses.com')
);
 
//Encode the array into JSON.
$jsonDataEncoded = json_encode($jsonData);
 
//Tell cURL that we want to send a POST request.
curl_setopt($ch, CURLOPT_POST, 1);
 
//Attach our encoded JSON string to the POST fields.
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonDataEncoded);
 
//Set the content type to application/json
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json')); 
 
//Execute the request
$result = curl_exec($ch);
?>

admin

Bio Info for Riaan