Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PHP

How to Use Amazon SES to Send Email from PHP

4.75/5 (3 votes)
11 Aug 2014CPOL2 min read 64.4K  
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

PHP
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:

PHP
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 = "";

}

?>

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)