Monday, June 24, 2013

How to use Amazon SES to Send Email from PHP


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: One pitfall with this is using the SMTP username and password instead of your AWS credentials.  Use your AWS credentials when sending emails using the SDK.

Otherwise, you may get 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


  1. Install the SDK
  2. Know if your account is sandboxed or not - If you account is sandboxed, you will only be able to send emails to the email addresses in the verified senders list.
  3. Make sure your source email's sending address is listed as a verified sender - Verify an email address that you own and use that in your sample code.  Check this email and the ReturnPath address in your AWS console if you are getting the "Email address is not verified" error.
  4. If you use the ReturnPath parameter (not shown here) to receive bounced emails, then that email address or domain must be verified as well

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.

Update:

Because of a request from Mohit Singh, I've updated the code to allow for adding a single attachment to the email.  See below for the details.

Using Attachments:

Use this example code in your program to send an email with an attachment using Amazon SES.

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 Source for PHP Solution for sending mail using SES

Update #2 - (2015-01-27) Michael Deal was kind enough to provide additional features and enhancements in this new version

Update #3 - (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!


<?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
 *
 * Update: 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 be mail 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 = "";

}

?>

Sending multiple attachments using SES - Completed!


The above is a more complete and robust version of the sending email attachments with Amazon SES.  The one above now lets you have more than one attachment to send from the previous version.  Hope you enjoyed this and thanks to all for participating! 



76 comments:

  1. You are my hero. Thank you so much for this post!

    ReplyDelete
    Replies
    1. You're welcome! Glad I could help. If you wouldn't mind adding a +1, I'd appreciate it.

      Delete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
  3. Despite the fact that there are a huge number of retail sites on the web, a couple of significant brands keep on overwhelming, much like customary high road retail. By some separation, Amazon is the biggest online retailer of all.Buy Niche Organic 100% Safe USA based Amazon Traffic

    ReplyDelete
  4. , you can procure a specialist PHP web designer so as to tweak your site according to your business prerequisites, which is very financially savvy. CakePHP Application Development

    ReplyDelete
  5. PHP's essential use is as an "implanted" scripting language, which implies that the real PHP code is inserted in HTML code.Why use Laravel

    ReplyDelete
  6. Find approved Motorola administration focuses close to you in . Discover Location. It would be ideal if you enter your city to view Service Centers that are approved to fix your gadget.
    Best mobile service centre.

    ReplyDelete
  7. You have a good point here!I totally agree with what you have said!!Thanks for sharing your views...hope more people will read this article!!!
    tree service near me in tequesta

    ReplyDelete
  8. Superbly written article, if only all bloggers offered the same content as you, the internet would be a far better place.
    bathroom remodel reno nv

    ReplyDelete
  9. Thanks for a wonderful share. Your article has proved your hard work and experience you have got in this field. Brilliant .i love it reading.
    septic tank cleaning west palm beach

    ReplyDelete
  10. I have read your article, it is very informative and helpful for me.I admire the valuable information you offer in your articles. Thanks for posting it..
    durham remodeling contractors

    ReplyDelete
  11. Hello, I have browsed most of your posts. This post is probably where I got the most useful information for my research. Thanks for posting, maybe we can see more on this. Are you aware of any other websites on this subject.
    ac installation royal palm beach

    ReplyDelete
  12. Great article and a nice way to promote online. I’m satisfied with the information that you provided
    bathroom remodelers raleigh

    ReplyDelete
  13. This post is good enough to make somebody understand this amazing thing, and I’m sure everyone will appreciate commercial ac unit west palm beach

    ReplyDelete
  14. You have a good point here!I totally agree with what you have said!!Thanks for sharing your views...hope more people will read this article!!!
    commercial screen enclosures cape coral

    ReplyDelete
  15. Very nice bro, thanks for sharing this with us. Keep up the good work and Thank you for sharing information tree trimmers port st lucie

    ReplyDelete
  16. Great blog and a great topic as well I really get amazed to read this. It’s really good.
    free airdrops

    ReplyDelete
  17. This comment has been removed by a blog administrator.

    ReplyDelete
  18. Great blog thanks for sharing Instagram and Facebook have provided an amazing place for new brands to grow and flourish. We can find the perfect niche for your brand on the best social media platforms. Marketing through social media brings forth global audience without all these physical boundaries. Analyze and take over the competition with ease with Adhuntt Media’s digital marketing tools and strategies.
    digital marketing company in chennai

    ReplyDelete
  19. Nice blog thanks for sharing You have come to the right place. Karuna Nursery Gardens is the ideal place to begin your journey into landscape gardening. Our specialists have built some of the finest landscape garden in Chennai that too at the best price and amazing service.
    plant nursery in chennai

    ReplyDelete
  20. Excellent blog thanks for sharing Pixies beauty Shop is the best place to buy cosmetics in Chennai. With thousands of premium imported brands to choose from, you’ll never run out of lipstick again. And don’t forget about the best offers and value they provide.
    beauty Shop in Chennai

    ReplyDelete
  21. כתיבה מעולה, אהבתי. אשתף עם העוקבים שלי.
    קבוצת גבאי פייסבוק

    ReplyDelete
  22. This comment has been removed by the author.

    ReplyDelete
  23. הדעות שלי קצת חלוקות בעניין הזה אבל ללא ספק כתבת מעניין מאוד.
    טבעות אירוסין זהב לבן

    ReplyDelete
  24. רציתי רק לשאול, אפשר לשתף את הפוסט בבלוג שלי
    רהיטים לסלון

    ReplyDelete
  25. לגמרי פוסט שדורש שיתוף תודה.
    פינות אוכל

    ReplyDelete
  26. מזל שנתקלתי בכתבה הזאת. בדיוק בזמן
    טיפול prp לשיער

    ReplyDelete
  27. אין ספק שזה אחד הנושאים המעניינים. תודה על השיתוף.
    אירוע בת מצווה

    ReplyDelete
  28. לגמרי פוסט שדורש שיתוף תודה.
    מארז ליולדת

    ReplyDelete
  29. תודה על השיתוף. מחכה לכתבות חדשות.
    ברוקרים

    ReplyDelete
  30. Great Information,it has lot for stuff which is informative.I will share the post with my friend.
    animal jewelry

    ReplyDelete
  31. The best way to spend your night in 31 Dec with Hong Kong Escorts Girls, call us for bookings

    ReplyDelete
  32. סופסוף מישהו שתואם לדעותיי בנושא. תודה.
    בלוק תמונה

    ReplyDelete
  33. אין ספק שזה אחד הנושאים המעניינים. תודה על השיתוף.
    התקנת אינטרקום

    ReplyDelete
  34. Thanks for sharing this nice informatione!
    financial modelling to give you confidence in your financial strategy and provide business valuations.

    ReplyDelete
  35. It’s difficult to find experienced people in this particular topic, however, you seem like you know what you’re talking about! Thanks
    Tech news

    ReplyDelete
  36. Very useful blog thanks for sharing IndPac India the German technology Packaging and sealing machines in India is the leading manufacturer and exporter of Packing Machines in India.

    ReplyDelete
  37. Awesome article, it was exceptionally helpful! I simply began in this and I'm becoming more acquainted with it better. The post is written in very a good manner and it contains many useful information for me. Thank you very much and will look for more postings from you.


    digital marketing blog
    digital marketing bloggers
    digital marketing blogs
    digital marketing blogs in india
    digital marketing blog 2020
    digital marketing blog sites
    skartec's digital marketing blog
    skartec's blog
    digital marketing course
    digital marketing course in chennai
    digital marketing training
    skartec digital marketing academy

    ReplyDelete
  38. Thanks for your sharing! The information your share is very useful to me and many people are looking for them just like me!


    We have collection of best 2020 sex doll to fulfill your desire, so if you need realistic female sex dolls then Love Doll Palace is largest online store for you , who are providing young love doll for you, these are made by silicone and TPE, which will give you full happiness at sex duration and you can enjoy with her at any position.

    ReplyDelete
  39. אין ספק שהפוסט הזה דורש שיתוף. תודה
    ניהול מוניטין בגוגל

    ReplyDelete
  40. סופסוף מישהו שתואם לדעותיי בנושא. תודה.
    השקעות מניבות

    ReplyDelete
  41. כתיבה מעולה, אהבתי. אשתף עם העוקבים שלי.
    עיצוב חווית משתמש

    ReplyDelete
  42. Awesome and interesting article. Great things you've always shared with us. Visit Kidoriman and see Kidoriman reviews show that how people are happy with our extensive services. Our high-quality products and clothes make our customer happy. As a result, they prefer us for the shopping every time.

    ReplyDelete
  43. Application programs are intended to do explicit errands to be executed through the PC and the working framework programs are utilized to deal with the inward elements of the PC to encourage utilization of use program.
    itools crack reddit

    ReplyDelete
  44. i am browsing this website dailly , and get nice facts from here all the time .

    ReplyDelete