How to Use Amazon SES to Send Email from PHP






4.75/5 (3 votes)
How to send mail using Amazon's SES (Simple Email Service)
Sending Mail Using Amazon's SES (Simple Email Service)
I couldn't find too many good examples for this online and the Amazon AWS PHP SDK had incomplete documentation for a SendEmail
function when I was researching this topic.
NOTE: My biggest flaw with this was I was using the SMTP username and password instead of my AWS credentials. Use your AWS credentials when sending emails using the SDK.
I was getting this error:
SignatureDoesNotMatch, Status Code: 403, AWS Request ID: xxxxx, AWS Error Type: client,
AWS Error Message: The request signature we calculated does not match the signature you provided.
Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
The Canonical String for this request should have been
Setup
It's easy to install the Amazon SDK using PEAR (per Amazon documentation):sudo pear -D auto_discover=1 install pear.amazonwebservices.com/sdk
Once you get the SDK, you also need to make sure you have the Amazon SES setup correctly to let you send emails. If you account is sandboxed, you will only be able to send emails to the email addresses in the verified senders list. Verify an email address that you own and use that in your sample code.
When you have the right credentials, sending Email using the SDK is very easy.
Sample Code
require 'AWSSDKforPHP/aws.phar';
use Aws\Ses\SesClient;
$client = SesClient::factory(array(
'key' => 'aws_key',
'secret' => 'aws_secret',
'region' => 'us-east-1'
));
//Now that you have the client ready, you can build the message
$msg = array();
$msg['Source'] = "authorized_aws_email@somewhere.com";
//ToAddresses must be an array
$msg['Destination']['ToAddresses'][] = "someone@somwhere.com";
$msg['Message']['Subject']['Data'] = "Text only subject";
$msg['Message']['Subject']['Charset'] = "UTF-8";
$msg['Message']['Body']['Text']['Data'] ="Text data of email";
$msg['Message']['Body']['Text']['Charset'] = "UTF-8";
$msg['Message']['Body']['Html']['Data'] ="HTML Data of email<br />";
$msg['Message']['Body']['Html']['Charset'] = "UTF-8";
try{
$result = $client->sendEmail($msg);
//save the MessageId which can be used to track the request
$msg_id = $result->get('MessageId');
echo("MessageId: $msg_id");
//view sample output
print_r($result);
} catch (Exception $e) {
//An error happened and the email did not get sent
echo($e->getMessage());
}
//view the original message passed to the SDK
print_r($msg);
Result
Run the above code using the correct information and your emails should be on their way. Make sure to set the SenderID, DKIM, and SPF on your domain and Amazon properly to prevent your emails getting marked as spam.
Sending Attachments
Use the example usage code below in your main program to send attachments using SES's sendRawEmail
function:
include_once("SESUtils.php");
$subject_str = "Some Subject";
$body_str = "<strong>Some email body</strong>";
$attachment_str = get_file_contents("/htdocs/test/sample.pdf");
//send the email
$result = SESUtils::deliver_mail_with_attachment(
array('email1@gmail.com', 'email2@lutz-engr.com'),
$subject_str, $body_str, 'sender@verifiedbyaws',
$attachment_str);
//now handle the result if you wish
print_r($result);
Complete Solution with Latest Updates
Update#1 - (2015-01-27) Michael Deal was kind enough to provide additional features and enhancements in this new version
Update#2 - (2015-03-03) Code has been updated to properly handle plaintext with HTML and multiple attachments. It's not handled quite the way you would think. Thank you RFC-2046!
Here is the supporting code to help the code above work. Save the code below as 'SESUtils.PHP' and include it in your main program. Happy emailing!
<?php require_once('AWSSDKforPHP/aws.phar'); use Aws\Ses\SesClient; /** * SESUtils is a tool to make it easier to work with Amazon Simple Email Service * Features: * A client to prepare emails for use with sending attachments or not * * There is no warranty - use this code at your own risk. * @author sbossen * http://righthandedmonkey.com * * Update1: Error checking and new params input array provided by Michael Deal * Update2: Corrected for allowing to send multiple attachments and plain text/html body * Ref: Http://stackoverflow.com/questions/3902455/smtp-multipart-alternative-vs-multipart-mixed/ */ class SESUtils { const version = "1.0"; const AWS_KEY = "YOUR-KEY"; const AWS_SEC = "YOUR-SECRET"; const AWS_REGION = "us-east-1"; const MAX_ATTACHMENT_NAME_LEN = 60; /** * Usage: $params = array( "to" => "email1@gmail.com", "subject" => "Some subject", "message" => "<strong>Some email body</strong>", "from" => "sender@verifiedbyaws", //OPTIONAL "replyTo" => "reply_to@gmail.com", //OPTIONAL "files" => array( 1 => array( "name" => "filename1", "filepath" => "/path/to/file1.txt", "mime" => "application/octet-stream" ), 2 => array( "name" => "filename2", "filepath" => "/path/to/file2.txt", "mime" => "application/octet-stream" ), ) ); $res = SESUtils::sendMail($params); * NOTE: When sending a single file, omit the key (ie. the '1 =>') * or use 0 => array(...) - otherwise the file will come out garbled * ie. use: * "files" => array( * 0 => array( "name" => "filename", "filepath" => "path/to/file.txt", * "mime" => "application/octet-stream") * * For the 'to' parameter, you can send multiple recipiants with an array * "to" => array("email1@gmail.com", "other@msn.com") * use $res->success to check if it was successful * use $res->message_id to check later with Amazon for further processing * use $res->result_text to look for error text if the task was not successful * * @param array $params - array of parameters for the email * @return \ResultHelper */ public static function sendMail($params) { $to = self::getParam($params, 'to', true); $subject = self::getParam($params, 'subject', true); $body = self::getParam($params, 'message', true); $from = self::getParam($params, 'from', true); $replyTo = self::getParam($params, 'replyTo'); $files = self::getParam($params, 'files'); $res = new ResultHelper(); // get the client ready $client = SesClient::factory(array( 'key' => self::AWS_KEY, 'secret' => self::AWS_SEC, 'region' => self::AWS_REGION )); // build the message if (is_array($to)) { $to_str = rtrim(implode(',', $to), ','); } else { $to_str = $to; } $msg = "To: $to_str\n"; $msg .= "From: $from\n"; if ($replyTo) { $msg .= "Reply-To: $replyTo\n"; } // in case you have funny characters in the subject $subject = mb_encode_mimeheader($subject, 'UTF-8'); $msg .= "Subject: $subject\n"; $msg .= "MIME-Version: 1.0\n"; $msg .= "Content-Type: multipart/mixed;\n"; $boundary = uniqid("_Part_".time(), true); //random unique string $boundary2 = uniqid("_Part2_".time(), true); //random unique string $msg .= " boundary=\"$boundary\"\n"; $msg .= "\n"; // now the actual body $msg .= "--$boundary\n"; //since we are sending text and html emails with multiple attachments //we must use a combination of mixed and alternative boundaries //hence the use of boundary and boundary2 $msg .= "Content-Type: multipart/alternative;\n"; $msg .= " boundary=\"$boundary2\"\n"; $msg .= "\n"; $msg .= "--$boundary2\n"; // first, the plain text $msg .= "Content-Type: text/plain; charset=utf-8\n"; $msg .= "Content-Transfer-Encoding: 7bit\n"; $msg .= "\n"; $msg .= strip_tags($body); //remove any HTML tags $msg .= "\n"; // now, the html text $msg .= "--$boundary2\n"; $msg .= "Content-Type: text/html; charset=utf-8\n"; $msg .= "Content-Transfer-Encoding: 7bit\n"; $msg .= "\n"; $msg .= $body; $msg .= "\n"; $msg .= "--$boundary2--\n"; // add attachments if (is_array($files)) { $count = count($files); foreach ($files as $file) { $msg .= "\n"; $msg .= "--$boundary\n"; $msg .= "Content-Transfer-Encoding: base64\n"; $clean_filename = self::clean_filename($file["name"], self::MAX_ATTACHMENT_NAME_LEN); $msg .= "Content-Type: {$file['mime']}; name=$clean_filename;\n"; $msg .= "Content-Disposition: attachment; filename=$clean_filename;\n"; $msg .= "\n"; $msg .= base64_encode(file_get_contents($file['filepath'])); $msg .= "\n--$boundary"; } // close email $msg .= "--\n"; } // now send the email out try { $ses_result = $client->sendRawEmail( array( 'RawMessage' => array( 'Data' => base64_encode($msg) ) ), array( 'Source' => $from, 'Destinations' => $to_str ) ); if ($ses_result) { $res->message_id = $ses_result->get('MessageId'); } else { $res->success = false; $res->result_text = "Amazon SES did not return a MessageId"; } } catch (Exception $e) { $res->success = false; $res->result_text = $e->getMessage(). " - To: $to_str, Sender: $from, Subject: $subject"; } return $res; } private static function getParam($params, $param, $required = false) { $value = isset($params[$param]) ? $params[$param] : null; if ($required && empty($value)) { throw new Exception('"'.$param.'" parameter is required.'); } else { return $value; } } /** Clean filename function - to get a file friendly **/ public static function clean_filename($str, $limit = 0, $replace=array(), $delimiter='-') { if( !empty($replace) ) { $str = str_replace((array)$replace, ' ', $str); } $clean = iconv('UTF-8', 'ASCII//TRANSLIT', $str); $clean = preg_replace("/[^a-zA-Z0-9\.\/_| -]/", '', $clean); $clean = preg_replace("/[\/| -]+/", '-', $clean); if ($limit > 0) { //don't truncate file extension $arr = explode(".", $clean); $size = count($arr); $base = ""; $ext = ""; if ($size > 0) { for ($i = 0; $i < $size; $i++) { if ($i < $size - 1) { //if it's not the last item, add to $bn $base .= $arr[$i]; //if next one isn't last, add a dot if ($i < $size - 2) $base .= "."; } else { if ($i > 0) $ext = "."; $ext .= $arr[$i]; } } } $bn_size = mb_strlen($base); $ex_size = mb_strlen($ext); $bn_new = mb_substr($base, 0, $limit - $ex_size); // doing again in case extension is long $clean = mb_substr($bn_new.$ext, 0, $limit); } return $clean; } } class ResultHelper { public $success = true; public $result_text = ""; public $message_id = ""; } ?>