{"id":266,"date":"2020-01-20T19:24:17","date_gmt":"2020-01-20T19:24:17","guid":{"rendered":"https:\/\/www.antpace.com\/blog\/?p=266"},"modified":"2025-08-25T14:01:16","modified_gmt":"2025-08-25T14:01:16","slug":"sending-email-from-your-app-using-aws-ses","status":"publish","type":"post","link":"https:\/\/www.antpace.com\/blog\/sending-email-from-your-app-using-aws-ses\/","title":{"rendered":"Sending email from your app using AWS SES"},"content":{"rendered":"<h2>Simple Email Service (SES) from AWS<\/h2>\n<p>Email is the best way that we can communicate with our users; still better than SMS or app notifications. An effective messaging strategy can enhance the journey our products offer.<\/p>\n<p>This post is about sending email from the website or app you&#8217;re developing. We will use SES to send transactional emails. AWS\u00a0<a href=\"https:\/\/docs.aws.amazon.com\/ses\/index.html\">documentation<\/a> describes\u00a0Simple Email Service (SES) as &#8220;an\u00a0email sending and receiving service that provides an easy, cost-effective way for you to send email.&#8221; It abstracts away managing a mail server.<\/p>\n<h2>Identity verification<\/h2>\n<p>When you first get set up, SES will be in sandbox mode. That means you can only send email to verified receivers. To get production access and start sending emails to the public, you will need to verify an email address and a sending domain.<\/p>\n<h3>Configuring your domain name<\/h3>\n<p>Sending email through SES requires us to verify the domain name that messages will be coming from. We can do this from the &#8220;Domains&#8221; dashboard.<\/p>\n<figure id=\"attachment_395\" aria-describedby=\"caption-attachment-395\" style=\"width: 1021px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-395 size-full\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/01\/verify-domain-name.png\" alt=\"Verify a new domain name\" width=\"1021\" height=\"366\" \/><figcaption id=\"caption-attachment-395\" class=\"wp-caption-text\">Verify a new domain name<\/figcaption><\/figure>\n<p>This will generate a list of record sets that will need to be added to our domain as DNS records. I use Route 53, another Amazon service, to manage my domains &#8211; so that&#8217;s where I&#8217;ll need to enter this info. If this is a new domain that you are working with, you will need to create a &#8220;hosted zone&#8221;in AWS for it first.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-400\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/01\/Route53.png\" alt=\"AWS Route 53\" width=\"1023\" height=\"464\" \/><\/p>\n<p>As of this update, Amazon recommends using CNAME DKIM (DomainKeys Identified Mail) records instead of TXT records to authenticate your domain. These signatures enhance the deliverability of your mail with DKIM-compliant email providers. If your domain name is in Route 53, SES will automatically import the CNAME records for you.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-2367 size-full\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/01\/Screen-Shot-2024-01-15-at-1.19.41-PM.png\" alt=\"warning message about legacy TXT records\" width=\"1178\" height=\"208\" \/><\/p>\n<h3>Understand deliverability<\/h3>\n<p>We want to be confident that intended recipients are actually getting the messages that are sent.\u00a0 Email service providers, and ISPs, want to prevent being abused by spammers. Following best practices, and understanding deliverability, can ensure that emails won&#8217;t be blocked.<\/p>\n<p>Verify any email addresses that you are sending messages from: &#8220;To maintain trust between email providers and Amazon SES, Amazon SES needs to ensure that its senders are who they say they are.&#8221;<\/p>\n<p>You should use an email address that is at the domain you verified. To host business email, I suggest AWS WorkMail or Google Workspace<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-411 size-full\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/01\/verify-email.png\" alt=\"verify a new email in AWS\" width=\"987\" height=\"290\" \/><\/p>\n<p>Make sure DKIM has been verified for your domain: \u00a0&#8220;DomainKeys Identified Mail (DKIM) provides proof that the email you send originates from your domain and is authentic&#8221;. If you&#8217;re already using Route 53 to manage your DNS records, SES will present an option to automatically create the necessary records.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-406\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/01\/dkim.png\" alt=\"Route 53 DKIM records\" width=\"1020\" height=\"427\" \/><\/p>\n<p>Be reputable. Send high quality emails and make opt-out easy. You don&#8217;t want to be marked as spam. Respect sending quotas. If you&#8217;re plan on sending bulk email to a list-serve, I suggest using an Email Service Provider such as MailChimp (SES could be used for that too, but is outside the scope of this writing).<\/p>\n<h2>Sending email<\/h2>\n<p>SES can be used three ways: either by API, the SMTP interface, or the console. Each method lists different ways to authenticate. &#8220;<a href=\"https:\/\/docs.aws.amazon.com\/ses\/latest\/DeveloperGuide\/using-credentials.html\">To interact with [Amazon SES], you use security credentials to verify who you are and whether you have permission to interact with Amazon SES.<\/a>&#8221; Next, we will use the API credentials &#8211; an access key ID and secret access key.<\/p>\n<h3>Create an access key pair<\/h3>\n<p><span style=\"font-size: 1rem;\">An access key can be created using\u00a0Identity and Access Management (IAM).\u00a0<\/span><span style=\"font-size: 1rem;\">&#8220;<a href=\"https:\/\/docs.aws.amazon.com\/general\/latest\/gr\/aws-sec-cred-types.html#access-keys-and-secret-access-keys\">You use access keys to sign programmatic requests that you make to AWS<\/a>.&#8221; This requires creating a user, and setting its permissions policies to include &#8220;AmazonSESSendingAccess&#8221;. We can create an access key in the &#8220;security credentials&#8221; for this user.<\/span><\/p>\n<figure id=\"attachment_424\" aria-describedby=\"caption-attachment-424\" style=\"width: 819px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-424 size-full\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/01\/iam-user.png\" alt=\"Permission policy for IAM user\" width=\"819\" height=\"494\" \/><figcaption id=\"caption-attachment-424\" class=\"wp-caption-text\">Permission policy for IAM user<\/figcaption><\/figure>\n<h3>Integrating with WordPress<\/h3>\n<p>Sending email from WordPress is made easy with plugins. <a href=\"https:\/\/contactform7.com\/\">They can be used to easily create forms<\/a>. Those forms can be wired to use the outbound mail server of our choice using\u00a0<a href=\"https:\/\/wpmailsmtp.com\/docs\/how-to-set-up-the-amazon-ses-mailer-in-wp-mail-smtp\/\">WP Mail SMTP Pro<\/a>. All we&#8217;ll need to do is enter the access key details. If we try to send email without specifying a mail server, forms will default to sending messages directly from the LAMP box hosting the website. That would result in low-to-no deliverability.<\/p>\n<figure id=\"attachment_426\" aria-describedby=\"caption-attachment-426\" style=\"width: 1086px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-426 size-full\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/01\/wpmailsmtp.png\" alt=\"Screenshot of WP Mail SMTP Pro\" width=\"1086\" height=\"730\" \/><figcaption id=\"caption-attachment-426\" class=\"wp-caption-text\">Screenshot of WP Mail SMTP Pro<\/figcaption><\/figure>\n<p>As of this update, the above plugin now only provides the &#8220;Amazon SES&#8221; option with a premium (not free) monthly subscription. That&#8217;s OK, because we can still use Amazon SES through the &#8220;Other SMTP&#8221; option. You can read about how I used this choice to <a href=\"https:\/\/www.antpace.com\/blog\/transform-your-site-a-case-study-on-redesigning-with-wordpress-gutenberg-on-aws-lightsail\/\">send emails from a WordPress site&#8217;s contact form<\/a>.<\/p>\n<h3>SMTP Username and Password<\/h3>\n<p>The &#8220;Other SMTP&#8221; option asks for a username and password. You can create those credentials from Amazon SES by going to &#8220;SMTP Settings&#8221;. When you click &#8220;<a class=\"awsui_button_vjswe_1im18_101 awsui_variant-primary_vjswe_1im18_214\" title=\"Create SMTP credentials (opens in a new tab)\" href=\"https:\/\/console.aws.amazon.com\/iamv2\/home?SESRegion=us-east-2#\/users\/smtp\/create\" target=\"_blank\" rel=\"noopener noreferrer\" aria-label=\"Create SMTP credentials (opens in a new tab)\" data-analytics-funnel-value=\"button11-1705372165906-4619\"><span class=\"awsui_content_vjswe_1im18_97\">Create SMTP credentials<\/span><\/a>&#8221; you will be redirected to the IAM service to create a user with the details already filled<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2371\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/01\/Screen-Shot-2024-01-15-at-9.30.42-PM.png\" alt=\"creating a new IAM user for SMTP\" width=\"1703\" height=\"1004\" \/><\/p>\n<p>It will give you the SMTP user name (different than the IAM user name) and password on the following screen. After you add these details to the plugin, any emails sent from this WordPress instance will use SES as the mail server. As a use case, I create forms with another plugin called &#8220;Contact Form 7&#8221;. Any emails sent through these forms will use the above set up.<\/p>\n<h3>Integrating with custom code<\/h3>\n<p>Although the WordPress option is simple, the necessary plugin has an annual cost. Alternatively, SES can integrate with custom code we&#8217;ve written. We can use\u00a0<a href=\"https:\/\/github.com\/PHPMailer\/PHPMailer\">PHPMailer<\/a> to abstract away the details of sending email programmatically. Just include the necessary files, configure some variables, and call a send() method.<\/p>\n<figure id=\"attachment_468\" aria-describedby=\"caption-attachment-468\" style=\"width: 1000px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-468\" src=\"https:\/\/www.antpace.com\/blog\/wp-content\/uploads\/2020\/01\/contact-form-portfolio-1.png\" alt=\"Contact form powered by SES\" width=\"1000\" height=\"631\" \/><figcaption id=\"caption-attachment-468\" class=\"wp-caption-text\">Contact form powered by SES<\/figcaption><\/figure>\n<p>The contact forms on my\u00a0<a href=\"https:\/\/www.antpace.com\/resume\/\">r\u00e9sum\u00e9<\/a>\u00a0 and <a href=\"https:\/\/www.antpace.com\/portfolio\/\">portfolio<\/a>\u00a0webpages use this technique. I submit the form data to a PHP file that uses PHPMailer to interact with SES. The front-end uses a UI notification widget to give the user alerts. <a href=\"https:\/\/github.com\/pacea87\/ui-messages\">It&#8217;s available on my GitHub, so check it out<\/a>.<\/p>\n<p>Front-end, client-side:<\/p>\n<pre>&lt;form id=\"contactForm\"&gt;\n    &lt;div class=\"outer-box\"&gt;\n\n        &lt;input type=\"text\" placeholder=\"Name\" name=\"name\" value=\"\" class=\"input-block-level bordered-input\"&gt;\n        &lt;input type=\"email\" placeholder=\"Email\" value=\"\" name=\"email\" class=\"input-block-level bordered-input\"&gt;\n        &lt;input type=\"text\" placeholder=\"Phone\" value=\"\" name=\"phone\" class=\"input-block-level bordered-input\"&gt;\n\n        &lt;textarea placeholder=\"Message\" rows=\"3\" name=\"message\" id=\"contactMessage\" class=\"input-block-level bordered-input\"&gt;&lt;\/textarea&gt;\n        &lt;button type=\"button\" id=\"contactSubmit\" class=\"btn transparent btn-large pull-right\"&gt;Contact Me&lt;\/button&gt;\n    &lt;\/div&gt;\n&lt;\/form&gt;\n&lt;link rel=\"stylesheet\" type=\"text\/css\" href=\"\/ui-messages\/css\/ui-notifications.css\"&gt;\n&lt;script src=\"\/ui-messages\/js\/ui-notifications.js\"&gt;&lt;\/script&gt;\n&lt;script type=\"text\/javascript\"&gt;\n$(function(){\n\tvar notifications = new UINotifications();\n\t$(\"#contactSubmit\").click(function(){\n\t\tvar contactMessage = $(\"#contactMessage\").val();\n\t\tif(contactMessage &lt; 1){\n\t\t\tnotifications.showStatusMessage(\"Don't leave the message area empty.\");\n\t\t\treturn;\n\t\t}\n\t\tvar data = $(\"#contactForm\").serialize();\n\t\t$.ajax({\n\t\t\ttype:\"POST\",\n\t\t\tdata:data,\n\t\t\turl:\"assets\/contact.php\",\n\t\t\tsuccess:function(response){\n\t\t\t\tconsole.log(response);\n\t\t\t\tnotifications.showStatusMessage(\"Thanks for your message. I'll get back to you soon.\");\n\t\t\t\t$(\"form input, form textarea\").val(\"\");\n\t\t\t}\n\n\t\t});\n\t});\n});\n&lt;\/script&gt;\n<\/pre>\n<p>In the PHP file,\u00a0 we set the username and password as the access key ID and access key secret. Make sure the region variable matches what you&#8217;re using in AWS. <strong>#TODO<\/strong>: It would be best practice to record the message to a database. (The WordPress plugin from earlier handles that out-of-the-box). We might also send an additional email to the user, letting them know their note was received.<\/p>\n<p>Back-end, server-side:<\/p>\n<pre>&lt;?php\n\/\/send email via amazon ses\nuse PHPMailer\\PHPMailer\\PHPMailer;\nuse PHPMailer\\PHPMailer\\Exception;\n\n$name = \"\";\n$email = \"\";\n$phone = \"\";\n$message = \"\";\n\nif(isset($_POST[\"name\"])){\n\t$name = $_POST[\"name\"];\n}\nif(isset($_POST[\"email\"])){\n\t$email = $_POST[\"email\"];\n}\nif(isset($_POST[\"phone\"])){\n\t$phone = $_POST[\"phone\"];\n}\nif(isset($_POST[\"message\"])){\n\t$message = $_POST[\"message\"];\n}\n\n$region = \"us-east-1\"\n$aws_key_id = \"xxx\"\n$aws_key_secret = \"xxx\"\n\nrequire '\/var\/www\/html\/PHPMailer\/src\/Exception.php';\nrequire '\/var\/www\/html\/PHPMailer\/src\/PHPMailer.php';\nrequire '\/var\/www\/html\/PHPMailer\/src\/SMTP.php';\n\/\/ \/\/ Instantiation and passing `true` enables exceptions\n$mail = new PHPMailer(true);\ntry {\n\tif(strlen($message) &gt; 1){\n    \/\/Server settings\n\t    $mail-&gt;SMTPDebug = 2;                                       \/\/ Enable verbose debug output\n\t    $mail-&gt;isSMTP();                                            \/\/ Set mailer to use SMTP\n\t    $mail-&gt;Host       = 'email-smtp.' . $region . '.amazonaws.com';  \/\/ Specify main and backup SMTP servers\n\t    $mail-&gt;SMTPAuth   = true;                                   \/\/ Enable SMTP authentication\n\t    $mail-&gt;Username   = $aws_key_id;                     \/\/ access key ID\n\t    $mail-&gt;Password   = $aws_key_secret;                               \/\/ AWS Key Secret\n\t    $mail-&gt;SMTPSecure = 'tls';                                  \/\/ Enable TLS encryption, `ssl` also accepted\n\t    $mail-&gt;Port       = 587;                                    \/\/ TCP port to connect to\n\n\t    \/\/Recipients\n\t    $mail-&gt;setFrom('XXX@antpace.com', 'Portfolio');\n\t    $mail-&gt;addAddress(\"XXX@antpace.com\");     \/\/ Add a recipient\n\t    $mail-&gt;addReplyTo('XXX@antpace.com', 'Portfolio');\n\n\t    \/\/ Content\n\t    $mail-&gt;isHTML(true);                                  \/\/ Set email format to HTML\n\n\t    $mail-&gt;Subject = 'New message from your portfolio page.';\n\t    $mail-&gt;Body    = \"This message was sent from: $name - $email - $phone \\n Message: $message\";\n\t    $mail-&gt;AltBody = \"This message was sent from: $name - $email - $phone \\n Message: $message\";\n\n\t    $mail-&gt;send();\n\t    echo 'Message has been sent';\n\t}\n\n} catch (Exception $e) {\n    echo \"Message could not be sent. Mailer Error: {$mail-&gt;ErrorInfo}\";\n}\n\n?&gt;\n<\/pre>\n<p>The\u00a0technical side of sending email from software is straight-forward. The\u00a0strategy can be fuzzy and requires planning. Transactional emails have an advantage over marketing emails. Since they are triggered by a user&#8217;s action, they have more meaning. They have higher open rates, and in that way afford an opportunity.<\/p>\n<p>How can we optimize the usefulness of these emails? Be sure to create a recognizable voice in your communication that resonates your brand. Provide additional useful information, resources, or offers. These kind of emails are an essential part of the user experience and your product&#8217;s development.<\/p>\n<p><a href=\"https:\/\/github.com\/pacea87\/php-services\/blob\/master\/send-email-service.php\">You can find the code for sending emails this way on my GitHub<\/a>.<\/p>\n<h2>Alternative to SES<\/h2>\n<p>In a recent project, a client had WordPress set up through AWS lightsail. By default, email sending for WP users&#8217; new accounts and password reseting is not configured out-of-the-box (Unlike using WordPress.com, where it just works). They already were using Google Workspace for their email, so we decided to leverage that instead.<\/p>\n<p>We decided to use &#8216;app passwords&#8217;. We needed to make sure two-factor authentication was set up. This allowed us to generate a password that can be used as the SMTP password.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Simple Email Service (SES) from AWS Email is the best way that we can communicate with our users; still better than SMS or app notifications. An effective messaging strategy can enhance the journey our products offer. This post is about sending email from the website or app you&#8217;re developing. We will use SES to send &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.antpace.com\/blog\/sending-email-from-your-app-using-aws-ses\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Sending email from your app using AWS SES&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":3161,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[12,23,70,110,115],"class_list":["post-266","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-development","tag-aws","tag-cloud","tag-iam","tag-route-53","tag-ses"],"_links":{"self":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/266","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/comments?post=266"}],"version-history":[{"count":1,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/266\/revisions"}],"predecessor-version":[{"id":3162,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/266\/revisions\/3162"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/media\/3161"}],"wp:attachment":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/media?parent=266"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/categories?post=266"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/tags?post=266"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}