Amazon SES Blog

Amazon SES Now Supports Custom MAIL FROM Domains

The Amazon SES team is pleased to announce that, to increase your email authentication options, you can now use your own MAIL FROM domain when you send emails with SES.

First, a quick refresher on the different “source” addresses associated with an email: an email has a “From” address and a MAIL FROM address. The “From” address is the address that you pass to SES in the header of your email. This is the address that recipients see when they view your email in their inbox (RFC 5322). The MAIL FROM address (a.k.a. “envelope MAIL FROM”), on the other hand, is the address that the sending mail server (SES) transmits to the receiving mail server to indicate the source of the mail (RFC 5321). The MAIL FROM address is used by the receiving mail server to return bounce messages and other error notifications, and is only viewable by recipients if they inspect the email’s headers in the raw message source. By default, SES uses its own MAIL FROM domain ( or a subdomain of that) when it sends your emails.

Why use my own MAIL FROM domain?

You might choose to use your own MAIL FROM domain to give you more flexibility in complying with Domain-based Message Authentication, Reporting and Conformance (DMARC). DMARC is an email authentication protocol that relies on two other authentication protocols (Sender Policy Framework (SPF) and DomainKeys Identified Mail (DKIM)) to enable receiving mail servers to validate that an incoming email is authorized by the owner of the sending domain and has not been modified during transit.

An email can comply with DMARC in two ways: by satisfying the DKIM requirements and/or by satisfying the SPF requirements. You can use either method, but some senders prefer to use both DKIM and SPF for maximum deliverability. As established by DMARC, the requirements for each validation are as follows:

  • DKIM. The requirements to pass DKIM validation for DMARC are: 1) the message must have a valid DKIM signature, and 2) the domain in the DKIM signature must align with the domain in the “From” address in the header of the email. You can easily achieve DKIM validation with SES, which provides a tool (EasyDKIM) to DKIM-sign your messages automatically.
  • SPF. The requirements to pass SPF validation for DMARC are: 1) The domain in the MAIL FROM address of the email must authorize the sending mail server to send for it via a DNS record, and 2) the domain in the email’s “From” address must match the MAIL FROM domain. When SES uses its default MAIL FROM domain, the first SPF requirement is satisfied (because the MAIL FROM domain is, and the mail server is SES), but the second requirement is not satisfied. This is where the benefit of using your own MAIL FROM domain comes in – it enables you to meet that second SPF requirement.

Can I use any domain as my MAIL FROM domain?

The MAIL FROM domain you use with SES must be a subdomain of the verified identity you want to use it with. For example, a MAIL FROM domain of would be a legitimate MAIL FROM domain for verified domain or verified email address An additional requirement is that the MAIL FROM domain you use with SES must not be a domain that you use in a “From” address if the MAIL FROM domain is the destination of email feedback forwarding.

How do I set it up?

You configure an identity to use a specific MAIL FROM domain within the Identity Management part of the SES console, or by using the SES API. You also must publish MX and SPF records to your domain’s DNS server. When SES successfully detects the MX record, emails you send from the identity will use the specified MAIL FROM domain. For a full description of the set-up procedure, see the developer guide.

Will my sending process change?

No. After you configure a verified identity to use a specified MAIL FROM domain and SES successfully detects the required MX record, you simply continue to send emails in the usual way.

We hope you find this feature useful! If you have any questions or comments, let us know in the SES Forum or here in the comment section of the blog.

Welcome, Mandrill Customers!

After the recent announcement of changes with Mandrill, we have received many questions from Mandrill customers who are looking at options for email service providers.

Moving to a new platform can be daunting. If you want to understand what SES is and what it can do for you, check out our product detail page and Getting Started guide. We also have our re:Invent 2014 and 2015 presentations, with real-world examples on how to use the email sending and receiving capabilities of SES. Like many other AWS products, SES offers a free tier.

If you’re ready to dive in and start your migration, we have put together a list of resources to get you up and running with SES.

  • If you send emails through an SMTP interface, check out our SMTP endpoints.
  • If you send emails through a web API, take a look at the SES API. You can interact with the SES API directly through HTTPS, or use one of the many AWS SDKs that take care of the details for you. AWS SDKs are available for Android, iOS, Java, .NET, Node.js, PHP, Python, and Ruby.
  • If you are an API user, note that:

    • Mandrill’s send method corresponds to SES’s SendEmail.
    • Mandrill’s send-raw method corresponds to SES’s SendRawEmail.
    • There is no corresponding send_at parameter for SES – messages are queued for delivery when you make an email-sending call to SES.
    • There is no need to specify async – SES API calls are inherently asynchronous.
  • If you process incoming email, we offer the ability to receive those emails through SES. Through our inbound processing, you can save your emails to S3, receive SNS notifications, and perform custom logic using Lambda.
  • To help you maintain good deliverability, we can automatically DKIM-sign your emails using Easy DKIM. (You are also free to do that manually.)
  • SES offers a mailbox simulator you can use to test your application’s handling of scenarios such as deliveries, bounces, and complaints.

We want your transition to be simple. If you have any questions, we welcome you to our forums, or you can review our support options.

Welcome to SES!

Introducing the AWS Lambda Blueprint for Filtering Emails Received Through Amazon SES

When we set out to build an email receiving service for AWS, we wanted to make receiving email easy, but we also wanted to empower you to make your own decisions about how your inbound emails should be handled. With the goal of striking a balance between ease of use and flexibility, we integrated SES with AWS Lambda, which allows you to easily define your own filter function with arbitrary logic.

Today, we’ll discuss how you can build a simple customized filter function using the new AWS Lambda blueprint for filtering inbound email.

Defining your email filter

When you log into the AWS Lambda console and select “Create a Lambda Function,” you are presented with a list of blueprints you can use. One of these blueprints is called “inbound-ses-spam-filter”—we’ll use this blueprint in our example as a springboard to jumpstart our custom filter function.

Spam filter Lambda blueprint

The blueprint is simple: it checks whether the email has passed SPF and DKIM validations, and whether the email is deemed spam or virus. If an email fails one or more of these tests, the message is bounced using a SendBounce call to SES; otherwise, the rule engine is instructed to continue processing the message.

Once you’ve selected the blueprint, you can customize it to fit your individual needs. First, you’ll need to fill in your domain as the bounce sender:

var sendBounceParams = {
  BounceSender: 'mailer-daemon@<MYDOMAIN>.com',
  OriginalMessageId: messageId,
  MessageDsn: {
    ReportingMta: 'dns; <MYDOMAIN>.com',
    ArrivalDate: new Date(),
    ExtensionFields: []
  BouncedRecipientInfoList: []

The heart of the filtering logic, however, lies in the following if statement:

if (receipt.spfVerdict.status === 'FAIL' ||
        receipt.dkimVerdict.status === 'FAIL' ||
        receipt.spamVerdict.status === 'FAIL' ||
        receipt.virusVerdict.status === 'FAIL')

You can modify this conditional statement to fit your use case. Perhaps there’s a particular sender you want to block, or you noticed a pattern of spam with a particular header. For this example, we’ll bounce all emails with the subject line “Buy stuff!”

var mailMetadata = sesNotification.mail;

if (receipt.spfVerdict.status === 'FAIL' ||
        receipt.dkimVerdict.status === 'FAIL' ||
        receipt.spamVerdict.status === 'FAIL' ||
        receipt.virusVerdict.status === 'FAIL' ||
        mailMetadata.commonHeaders.subject === 'Buy stuff!')

The filter function will be invoked synchronously to ensure that it can return a disposition value back to SES, which will then use this value to decide whether to continue processing the message. This particular filter function returns “stop_rule_set” as the disposition after bouncing the message, effectively instructing SES to stop processing the message once the Lambda function returns.

  disposition: 'stop_rule_set'

Check out our documentation for more information about disposition values.

Because the filter function uses SES’s SendBounce API, you need to make sure that your Lambda function’s execution IAM role is allowed to call SendBounce. You can use the following example policy document, which allows your filter function to call SendBounce for any verified domain:

  "Version": "2012-10-17",
  "Statement": [
      "Effect": "Allow",
      "Action": [
      "Resource": "*"

Configuring your email filter

Now that you’ve defined your filter function, you need to configure it to process your inbound email stream. To do this, you can create an SES receipt rule that synchronously invokes an AWS Lambda function, which can be done either through the SES console or by using the CreateReceiptRule API (see our documentation to find out more about creating receipt rules). For this example, we’ll use the console’s receipt rule wizard to create our filter rule.

Lambda action

Note that the invocation type must be set to RequestResponse (synchronous) so that your filter function can decide whether or not the message should be filtered out.

In order to prevent your existing rules from being evaluated for unwanted emails, you need to make sure that your filter rule is processed before any of your processing rules. You can do this by setting the position of your new rule in Step 3 of the receipt rule wizard.

Rule position

Once you’ve created your filter rule, it will be shown at position 1 of your rule set:

Rule set

Note that the rule was configured without specific recipients, which causes the rule to be evaluated for all your inbound emails regardless of the intended recipient. You can always limit the scope of your filter rule by configuring specific recipients to match.

Testing your email filter

You can now test your filter function by sending an email to the address you’re listening in on. To simulate spam or virus, you can use GTUBE or EICAR, respectively, as the message content.

Receiving email programmatically has traditionally been a complex task, due to the open nature of email and the abuse vectors that come with it. We hope that you’ll find the new AWS Lambda blueprint for filtering inbound email to be a useful starting point to help you deal with unwanted mail and manage your operating costs.

Happy hacking!

Amazon SES Best Practices: Top 5 Best Practices for List Management

If you are an Amazon SES customer, you probably know that in addition to managing your email campaigns, you need to be mindful of your reputation as an email sender.

Maintaining a good reputation as a sender is vital if you rely on email delivery as part of your business – if you have a good reputation, your emails have the best chance of arriving in your recipients’ inboxes. If your reputation is questionable, your messages could get dropped or banished to the spam folder. Recipient ISPs may also decide to throttle your email, preventing you from delivering emails to your recipients on time.

This blog post provides five best practices to help you keep your email-sending reputation and deliverability high by focusing on the source of most deliverability problems: list acquisition and management.

Without further ado, here are our Top 5 Best Practices for List Management:

1. Use confirmed opt-in (a.k.a. double opt-in or the gold standard).

The principle behind this is simple – when a user enters an email address on your website, you need to verify that the address is legitimate before you add it to the mailing list you use for your regular campaigns. To this end, you send a verification email to the address and ask the subscriber to click a link in the email, which will then enable the account. By clicking on this link, the email address owner is confirming that they are willing to receive the email notifications they signed up for on your website. The benefits of this practice are evident:

  • You will not send to an email address more than once (or a few times, if the customer requests a second verification email). If the address is fake (or a typo) and the email is sent to someone who doesn’t want to hear from you, then you are less likely to get a complaint from this person because they will only get one email.
  • Since your actual mail campaigns are only going to addresses you have verified, then you know that you are making good use of your resources and that your campaigns are actually appreciated by the recipients.

2. Process bounces and complaints.

SES provides feedback on bounces and complaints through SNS (or email) to make it easy for you to be alerted of addresses that bounce or recipients who complain. If you get a hard bounce or a complaint, you should remove that email address from your list. You should also identify the root cause of the bounces and complaints. For example, say that you notice that your bounce rate for new subscriptions is rising. This could be an indicator that people are signing up for your service using fake email addresses. While it is not unusual for someone to sign up using a fake email address, you need to make sure that you are not encouraging your customers to do so. One way in which you could be encouraging customers to do this is by giving away free stuff without asking for a confirmed opt-in. If you are in this situation, you need to change the incentive that drives customers to sign up using fake addresses: either remove the gifts or implement confirmed opt-in (there is a reason we call this the gold standard J).

3. Remove non-engagers.

You need to operate under the assumption that if a customer is not opening or clicking your email, then they are not interested in what you’re sending. Define a timeframe that makes sense to your business, and if a recipient doesn’t interact with your mail within that timeframe, stop emailing them. This tactic is a great complement to double opt-in and should be standard for any email sender. Regardless of whether a customer originally opted in through double opt-in or just a regular signup, an email address can go stale and become a spamtrap.  Spamtraps are silent reputation traps, which means that you will get no indication that you are hitting them – removing non-engagers is the only way to avoid them. They are used by many organizations to measure a sender’s reputation, and particularly how well the sender is measuring engagement. If you continue to email spamtraps, your mail could end up in the spam folder, your domain could be blacklisted, and SES could suspend your service.

4. Make it easy for your recipients to unsubscribe.

If you are sending bulk email (as opposed to mail that is the result of a transaction), then you need to make it easy for customers to opt out of the mail. Include an easy-to-spot opt-out link in every bulk email, and use the list-unsubscribe header for easy integration with ISPs who support it. If a customer does not want the mail, you should not send it to them. Sending email to an unwilling recipient will do more harm than good. In many locations, including the US, Canada, and much of Europe and Asia, enabling recipients to easily opt out of your email is a legal requirement.

5. Keep your mailing lists independent.

If you operate more than one website, you should never mix your subscriber lists. Customers who sign up for website A should never (under any circumstance) receive an email from website B, unless they sign up for that one too. The reason is simple: These customers have only agreed to receive email from website A. Furthermore, if your customers get mail from a website unknown to them, they are likely to mark that mail as spam, thus hurting your email reputation.

Never forget: Your email campaigns are only as good as their ability to reach your customers, and following best practices can be the difference between a delivered and a dropped email. While the above best practices should help you, list management is only a part of the equation – the quality of your content also plays a big role in your ability to deliver email. Nevertheless, we hope our recommendations in this post will prove useful in your email endeavors.

If you have questions, feel free to let us know via the SES Forums or in the comment section of this blog.

Receiving Email with Amazon SES

The Amazon SES team is pleased to announce that you can now use SES to receive email!

For the past four years, SES has strived to make your life easier by maintaining a fleet of SMTP servers ready to send mail when you want it. There’s no need to worry about scaling, ensuring message delivery, or navigating relationships with countless email service providers.

However, you’d still need to manage a fleet of SMTP servers if you wanted to receive mail. As with sending mail, receiving mail comes with its own set of headaches: scaling for traffic spikes, blocking malicious senders, filtering out spam and viruses, and ultimately routing mail to your application, to name a few.

As of today, the SES team would like to invite you to say goodbye to these hassles, and rely on SES to simply receive your mail just as you rely on us to simply send your mail.

Why should I use SES to receive mail?

SES is ideally suited for servicing mail that is programmatically actionable. The following are a handful of common use cases that you can now leverage SES to solve:

  • Automatically create support tickets from customer email.
  • Implement an email auto-responder.
  • Process email list unsubscribe requests.
  • Process email bounces and complaints.
  • Create an email archival solution.
  • Update correspondence in tickets, forums, etc. by email.
  • Receive files from customers via email.

You can also use SES to manage your organization’s entire mail stream, directing mail destined to personal inboxes to Amazon WorkMail and processing customer service mail, etc. programmatically with SES.

How does it work?

Think of SES as an email gateway to the AWS ecosystem. After onboarding your domain onto SES, we will receive mail on your behalf, and allow you to consume it through a variety of different AWS services. For example, you can configure SES to deliver all of your mail to an Amazon S3 bucket, and process it directly using AWS Lambda.

SES empowers you to make decisions about how your mail is processed through the concept of a rule set. Every account that receives mail using SES has a single active rule set that you customize to dictate to SES what you’d like done with your mail across all of your SES-managed domains.

A rule set is simply an ordered list of rules, and a rule is a combination of a matching condition and an ordered list of actions. A condition is something like “All mail to” or “All mail to and all subdomains.” Actions are things like “Encrypt my mail using my AWS KMS key, write it to my S3 bucket, and notify me of the delivery via Amazon SNS” or “Asynchronously execute my Lambda function that updates my mailing list based on unsubscribe emails” or “Send me a SNS notification containing the email.” A more thorough discussion of rule sets, rules, and actions can be found in our developer guide.

Your rule set is sequentially evaluated for every message SES receives, and only the actions that apply to the message are executed. This enables you to write rules that route mail differently based on individual message characteristics. You can have a rule that drops mail that SES flags as spam across all of your domains, another that writes mail to to one S3 bucket, another that writes mail to to a different bucket and then executes a Lambda function but only when the email contains a specific header value, and so on.

Amazon SES rule set

The system was designed to be both highly customizable and convenient to use. Our goal is to minimize the amount of custom email routing or parsing logic that your application needs to do, and, if you capitalize on our Lambda integration, you may not even need an application at all!

How do I get started?

The best place to start is the SES developer guide. It provides detailed instructions on how to onboard a domain onto SES to receive mail, as well as walks you through the process of setting up rules to govern your mail flows. Then, head over to the SES console to set up your domains to begin receiving mail!

Finally, if you’re heading to AWS re:Invent this year, be sure to check out our presentation showcasing our new features!

Announcing Sending Authorization!

The Amazon SES team is excited to announce the release of sending authorization! This feature allows users to grant permission to use their email addresses or domains to other accounts or IAM users.

Note that for simplicity, we’ll be referring to email addresses and domains collectively as “identities” and the accounts and IAM users receiving permissions as “delegate senders.”

Why should I use sending authorization?

The primary incentive to use sending authorization is to enable cross-account identity usage with fine-grained permission control. Let’s look at two example use cases.

Say you’ve just been hired to create and manage an email marketing campaign for an online retailer. Until now, in order to send the retailer’s marketing emails under their domain name, you would have had to convince them to allow you to verify their domain under your own AWS account—this would let you send emails using any address under their domain, at any time, and for any purpose, which the retailer might not be comfortable with. You’d also have to work out who would get the bounce/complaint/delivery notifications, which might be additionally confusing because the notifications from your marketing emails would be sent to the same place as the notifications from the transactional emails the retailer is handling.

With sending authorization, however, you can use the retailer’s identity and receive delivery, bounce and complaint notifications while letting them retain sole ownership of it. Identity owners will still be able to monitor usage with delivery, bounce, and complaint notifications and can adjust permissions at any time, and use AWS condition keys to finely control the scope of those permissions.

Imagine instead that you own or administrate for a company that has several disparate teams that all wish to use SES to send emails using a common email address. Until now, you would have had to create and maintain IAM users for each of these teams under the same account (in which case they still would have access to each other’s identities) or verify the same identity under multiple different accounts.

With sending authorization, you can verify the common identity under the single account (perhaps yours) and simply grant the other teams permission to use it. If you still prefer the IAM policy route, you can take advantage of the new condition keys released with sending authorization to tighten up the IAM policies.

Sending authorization is designed to be powerful and flexible. In fact, Amazon WorkMail uses sending authorization to provide an enterprise-level email and calendaring service built on SES.

How does sending authorization work?

Identity owners grant permissions by creating authorization policies. Let’s look at an example. The policy below gives account 9999-9999-9999 permission to use the domain owned by 8888-8888-8888 in SendEmail and SendRawEmail requests as long as the “From” address is (with any address tags).

  "Id": "SampleAuthorizationPolicy",	
  "Version": "2012-10-17",
  "Statement": [
      "Sid": "AuthorizeMarketer",
      "Effect": "Allow",
      "Resource": "arn:aws:ses:us-east-1:888888888888:identity/",
      "Principal": {"AWS": ["999999999999"]},
      "Action": ["SES:SendEmail", "SES:SendRawEmail"],
      "Condition": {
        "StringLike": {
          "ses:FromAddress": "marketing+.*"

You could write this policy yourself, or you could use the Policy Generator in the SES console, which is even easier. Your Policy Generator page would look like:

Policy generator

Identity owners can add or create a policy for an identity using the PutIdentityPolicy API or the SES console, and can have up to 20 different policies for each identity. You can read more about how to construct and use policies in our developer guide.

How do I make a call with someone else’s identity that I have permission to use?

You’ll specify to SES that you’re using someone else’s identity by presenting an ARN when you make a request. The ARN below refers to an example domain identity ( owned by account 9999-9999-9999 in the US West (Oregon) AWS region.


Depending on how you make your call, you may need to provide up to three different ARNs: a “source” identity ARN, a “from” identity ARN, and a “return-path” identity ARN. The SendEmail and SendRawEmail APIs have new optional parameters for this purpose, but users of our SMTP endpoint or our SendRawEmail API have the option to instead provide the ARNs as X-headers (X-SES-Source-ARN, X-SES-From-ARN, and X-SES-Return-Path-ARN). See our SendEmail and SendRawEmail documentation for more information about these identities). These headers will be removed by SES before your email is sent.

What happens to notifications when email is sent by a delegate?

Both the identity owner and the delegate sender can set their own bounce, complaint, and delivery notification preferences. SES respects both sets of preferences independently. As a delegate sender, you can configure your notification settings almost as you would if you were the identity owner. The two key differences are that you use ARNs in place of identities, and cannot configure feedback forwarding (a.k.a. receiving bounces and complaints via email) in the console or the API. But, this doesn’t mean that delegate senders cannot use feedback forwarding. If you are a delegate sender and you do want bounces and complaints to be forwarded to an email address you own, just set the “return-path” address of your emails to an identity that you own. You can read more about it in our developer guide.

Billing, sending limits, and reputation

Cross-account emails count against the delegate’s sending limits, so the delegate is responsible for applying for any sending limit increases they might need. Similarly, delegated emails get charged to the delegate’s account, and any bounces and complaints count against the delegate’s reputation.

Sending authorization and IAM policies

It’s important to distinguish between SES sending authorization policies and IAM policies. Although the policies look similar at first glance, sending authorization policies dictate who is allowed to use an SES identity, and IAM policies (set using AWS Identity Access and Management) control what IAM users are allowed to do. The two are independent. Therefore, it’s entirely possible for an IAM user to be unable to use an identity despite having authorization from the owner because the user’s IAM policies do not give permission to use SES (and vice versa). Keep in mind, however, that by default, IAM users with SES access are allowed to use any identities owned by their parent account unless a sending authorization policy explicitly dictates otherwise.

On a related note, with the release of sending authorization, we’re externalizing several new condition keys that you can use in your sending authorization and/or IAM policies:

  • ses:Recipients
  • ses:FromAddress
  • ses:FromDisplayName
  • ses:FeedbackAddress

These can be used to control when policies apply. For example, you might use the “ses:FromAddress” condition key to write an IAM policy that only permits an IAM user to call SES using a certain “From” address. For more information about how to use our new condition keys, see our developer guide.

We hope you find this feature useful! If you have any questions or comments, let us know in the SES Forum or here in the comment section of the blog.

SES Limit Increase Form Consolidation

Hi SES Senders,

We on the SES team strive to make things easier for our customers. As such, we’ve recently streamlined SES’s limit increase request process. Instead of having separate Support Center forms for Production Access and Sending Limit increases, we now have one form that serves both purposes. Our motivation behind the change was not just to consolidate the forms. The concept of “Production Access” was a little confusing, so we took this opportunity to deprecate that terminology.

Backing up a bit: With respect to SES, your AWS account can be in one of two states: in the sandbox, or out of the sandbox. “In the sandbox” means two things: 1) You can only send to verified email addresses and domains, and 2) Your sending limits are set to their starting values, which is a sending quota of 200 emails per 24-hour period, and a maximum send rate of 1 email per second. None of that has changed.

What’s changed is the process of getting out of the sandbox. Previously, to get out of the sandbox, you needed to submit an SES Production Access limit increase request via Support Center. A granted request would raise your sending limits and also remove the limitation on “To” addresses. If you later found that you needed higher sending limits, you’d submit a separate form in Support Center called an SES Sending Quota request.

Now, there is just one form: SES Sending Limits. With your first sending limit increase, you’ll automatically be moved out of the sandbox. That is, in addition to increasing your sending limits, you’ll be able to send to any “To” address.

The new form is very similar to the old SES Sending Quota form. It’s still in Support Center. You can click this link to go directly to the new form. However, if you ever need to get to it from the Support Center console, click the Create Case button. On the Create Case form, choose Service Limit Increase and then select SES Sending Limits from the drop-down menu as shown below.

Create SES sending limit increase case

Once you get to the form, you then choose the AWS region. Remember that sending limits apply to each region separately, and that also applies to being in or out of the sandbox. As always, your AWS account starts out in the sandbox in all three regions in which SES is available. Then, for example, if you want to get out of the sandbox in US East (N. Virginia) and EU (Ireland), you’ll need to submit a separate request for both regions. You can do this all on the same form by clicking the Add another request button after your previous request.

Below the region, you’ll select the limit type. SES has two sending limits: daily sending quota (the number of emails you can send in a 24-hour period) and maximum send rate (the maximum number of emails that SES can accept from your account per second, though note that the actual rate at which SES accepts your messages might be less than the maximum send rate). You can choose only one of the two limits in the drop-down menu, although if you really need to request increases in both, you can use the Add another request button to submit a request for the other limit. However, customers are most often interested in raising their daily sending quota.

SES sending limit increase

Once you choose the limit type, a field will appear for you to enter the amount. Be sure to only request the amount you really need. Keep in mind that you are not guaranteed to receive the amount you request, and the higher the limit you request, the more justification you’ll need to be considered for that amount.

The next fields are your mail type and your website URL. Although the website URL isn’t required, we highly recommend that you provide one if you have it, because it helps us evaluate your request.

The next three fields are to make sure that your sending is a good fit for our platform:

Finally, there is the Use Case field. This is where you should explain your situation in as much detail as possible; for example, describe the type of emails you are sending and how email-sending fits into your business. The more information you can provide that indicates that you are sending high-quality emails to recipients who want and expect it, the more likely we are to approve your request. The higher the jump you are requesting from your existing quota, the more detail you should provide.

SES Sending Limit cases are generally processed within one business day, but plan ahead and don’t wait until your situation is critical to submit the request.

We hope that consolidating our form and deprecating the “Production Access” terminology makes the SES limit increase process more straight-forward. If you have questions or comments, feel free let us know on the SES forum.

SES and Haskell

by Nolan Sandberg | on | Permalink | Comments |  Share


Amazon SES and most of the other AWS services have SDKs for languages like Java, .NET, PHP, Python, and Ruby. Most SDKs are just wrappers around the HTTP APIs that the services provide. If your favorite language isn’t supported by an AWS SDK, you can write your own client or use third-party APIs to call SES. In this post we are going to look at implementing a very minimal SES client in Haskell, a purely functional programming language.

I’ll cover some basic housekeeping before moving forward with the tutorial. In order to best follow this tutorial, you will need to have an intermediate level of understanding of Haskell and a basic understanding of SES. Haskell has several string-like types and this can create some confusion for beginners. String and Text are for textual data while ByteString is usually for binary data. Since the cryptographic functions provided in the cryptohash library work on ByteStrings, we will use ByteString for essentially everything in our example with the exception of error messages and parsing SES responses. We will also use the http-conduit library to make the HTTP requests to AWS and the GHC extension OverloadedStrings so we can type ByteStrings as string literals. This blog will be in the form of a literate haskell file where the code and explanations are together. First we will show the imports of libraries that we will be using.

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Control.Applicative
import Control.Monad
import Crypto.Hash (Digest, SHA256, hmac, hmacGetDigest, hash, digestToHexByteString)
import Data.Byteable (toBytes)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Base16 as B16 (encode)
import qualified Data.ByteString.Char8 as C
import Data.CaseInsensitive (original)
import Data.Char (toLower)
import Data.Text (Text, unpack)
import Data.Time (getCurrentTime)
import Data.Time.Format (formatTime, FormatTime)
import Data.Time.Clock (UTCTime)
import Data.List (intersperse, lines, sortBy)
import Data.Monoid ((<>))
import Network.HTTP.Conduit
import Network.HTTP.Types
import Network.HTTP.Types.Header
import Network.HTTP.Types.Method
import System.Locale (defaultTimeLocale)
import System.Environment (getEnv)

import Blaze.ByteString.Builder (toByteString)
import Data.Aeson

Signing AWS Requests

The first and arguably most difficult part of calling SES via HTTP is signing requests for authentication. We are going to implement the latest version of the request signing process which is documented here.

First, we need a function that can create a canonical request from the raw HTTP request. This essentially comes down to appending several parameters separated by newlines.

canonicalRequest :: Request -> ByteString -> ByteString
canonicalRequest req body = C.concat $
    intersperse "n"
        [ method req
        , path req
        , queryString req
        , canonicalHeaders req
        , signedHeaders req
        , hexHash body
hexHash :: ByteString -> ByteString
hexHash p = digestToHexByteString (hash p :: Digest SHA256)

The canonical headers parameter is created by separating each header name and header value by a colon and then separating each of those strings with a new line. In http-conduit, the host header is not included in the requestHeaders field of the Request record so we need to add that manually.

headers :: Request -> [Header]
headers req = sortBy ((a,_) (b,_) -> compare a b) (("host", host req) : requestHeaders req)

canonicalHeaders :: Request -> ByteString
canonicalHeaders req =
    C.concat $ map ((hn,hv) -> bsToLower (original hn) <> ":" <> hv <> "n") hs
  where hs = headers req

bsToLower :: ByteString -> ByteString
bsToLower = toLower

The signed headers parameter is a list of lowercase header names separated by semicolons.

signedHeaders :: Request -> ByteString
signedHeaders req =
    C.concat . intersperse ";"  $ map ((hn,_) -> bsToLower (original hn)) hs
  where hs = headers req

Now we have to create the derived key. The HMAC algorithm takes a key and plaintext and returns a fixed length string. We create the derived key by repeatedly using the HMAC algorithm to hash a value and then using the returned value as the key for the subsequent hash. The starting key is the user’s AWS secret access key prepended by the string “AWS4”. The values that will be hashed are the date, region, service, and finally, the string “aws4_request”.

v4DerivedKey :: ByteString -> -- ^ AWS Secret Access Key
                ByteString -> -- ^ Date in YYYYMMDD format
                ByteString -> -- ^ AWS region
                ByteString -> -- ^ AWS service
v4DerivedKey secretAccessKey date region service = hmacSHA256 kService "aws4_request"
  where kDate = hmacSHA256 ("AWS4" <> secretAccessKey) date
        kRegion = hmacSHA256 kDate region
        kService = hmacSHA256 kRegion service

hmacSHA256 :: ByteString -> ByteString -> ByteString
hmacSHA256 key p = toBytes $ (hmacGetDigest $ hmac key p :: Digest SHA256)

Next, we create the string to sign. This string contains the information we have computed up to this point along with the AWS region, the AWS service name, and the current time. For SES, the service name is “ses”.

stringToSign :: UTCTime    -> -- ^ current time
                ByteString -> -- ^ The AWS region
                ByteString -> -- ^ The AWS service
                ByteString -> -- ^ Hashed canonical request
stringToSign date region service hashConReq = C.concat
    [ "AWS4-HMAC-SHA256n"
    , C.pack (formatAmzDate date) , "n"
    , C.pack (format date) , "/"
    , region , "/"
    , service
    , "/aws4_requestn"
    , hashConReq

format :: UTCTime -> String
format = formatTime defaultTimeLocale "%Y%m%d"

formatAmzDate :: UTCTime -> String
formatAmzDate = formatTime defaultTimeLocale "%Y%m%dT%H%M%SZ"

Finally, we create the signature by combining the canonical request, the string to sign, and the derived key. Although the Request type has a field for the request body, we explicitly pass the body of the request around because we only want to deal with strict ByteStrings for simplicity.

createSignature ::  Request         -> -- ^ Http request
                    ByteString      -> -- ^ Body of the request
                    UTCTime         -> -- ^ Current time
                    ByteString      -> -- ^ Secret Access Key
                    ByteString      -> -- ^ AWS region
createSignature req body now key region = v4Signature dKey toSign
  where canReqHash = hexHash $ canonicalRequest req body
        toSign = stringToSign now region "ses" canReqHash
        dKey = v4DerivedKey key (C.pack $ format now) region "ses"

v4Signature :: ByteString -> ByteString -> ByteString
v4Signature derivedKey payLoad = B16.encode $ hmacSHA256 derivedKey payLoad

With the version 4 signing implemented, we can move on to implementing the SendEmail call.

SES SendEmailCall

SES is currently available in three AWS regions: us-east-1, us-west-2, and eu-west-1. The HTTP endpoints for SES can be found here.

We define a simple record that carries all of the information required to call the SendEmail API.

data SendEmailRequest = SendEmailRequest
    { region            :: ByteString
    , accessKeyId       :: ByteString
    , secretAccessKey   :: ByteString
    , source            :: ByteString
    , to                :: [ByteString]
    , subject           :: ByteString
    , body              :: ByteString
    } deriving Show

usEast1 :: ByteString
usEast1 = "us-east-1"

usWest2 :: ByteString
usWest2 = "us-west-2"

euWest1 :: ByteString
euWest1 = "eu-west-1"

The http-conduit library parses the URL and then we configure it further based on the parameters of the SendEmailRequest. By setting the accept header to “text/json”, SES will return a response in JSON which we can then parse with the aeson library. The x-amz-date header is required to make AWS requests. Finally, we sign the request and add the authentication header before sending the request to SES.

sendEmail :: SendEmailRequest -> IO (Either String SendEmailResponse)
sendEmail sendReq = do
    fReq <- parseUrl $ "https://email." ++ C.unpack (region sendReq) ++ ""
    now <- getCurrentTime
    let req = fReq
                { requestHeaders =
                    [ ("Accept", "text/json")
                    , ("Content-Type", "application/x-www-form-urlencoded")
                    , ("x-amz-date", C.pack $ formatAmzDate now)
                , method = "POST"
                , requestBody = RequestBodyBS reqBody
        sig = createSignature req reqBody now (secretAccessKey sendReq) (region sendReq)
    resp <- withManager (httpLbs (authenticateRequest sendReq now req reqBody))
    case responseStatus resp of
        (Status 200 _) -> return $ eitherDecode (responseBody resp)
        (Status code msg) ->
            return $ Left ("Request failed with status code <" ++
                show code ++ "> and message <" ++ C.unpack msg ++ ">")
    reqBody = renderSimpleQuery False $
                    [ ("Action", "SendEmail")
                    , ("Source", source sendReq)
                    ] ++ toAddressQuery (to sendReq) ++
                    [ ("Message.Subject.Data", subject sendReq)
                    , ("Message.Body.Text.Data", body sendReq)

authenticateRequest :: SendEmailRequest -> UTCTime -> Request -> ByteString -> Request
authenticateRequest sendReq now req body =
    req { requestHeaders =
            authHeader now (accessKeyId sendReq)
                           (signedHeaders req) sig
                           (region sendReq) :
                           (requestHeaders req)
  where sig = createSignature req body now (secretAccessKey sendReq) (region sendReq)
toAddressQuery :: [ByteString] -> SimpleQuery
toAddressQuery tos =
    zipWith (index address ->
                ( "Destination.ToAddresses.member." <>
                    C.pack (show index)
                , address)
            ) [1..] tos

authHeader ::   UTCTime     -> -- ^ Current time
                ByteString  -> -- ^ Secret access key
                ByteString  -> -- ^ Signed headers
                ByteString  -> -- ^ Signature
                ByteString  -> -- ^ AWS Region
authHeader now sId signHeads sig region =
    ( "Authorization"
    , C.concat
        [ "AWS4-HMAC-SHA256 Credential="
        , sId , "/"
        , C.pack (format now) , "/" 
        , region
        , "/ses/aws4_request, SignedHeaders="
        , signHeads
        , ", Signature="
        , sig

SES provides a response to successful requests with the request ID and message ID. To handle SES responses, we will use the Either type and follow a common practice in Haskell, which is to use the Left side for information about a failure and the Right side for the result of a success. If we receive any response code other than HTTP success code 200, we will return an error in the left side of the Either type. For successful calls, we will use aeson to decode the request. We have created a data structure to hold the request and message IDs and a FromJSON instance to decode the JSON.

data SendEmailResponse = SendEmailResponse
    { requestId     :: Text
    , messageId     :: Text
    } deriving Show

instance FromJSON SendEmailResponse where
    parseJSON (Object o) = do
        response <- o .: "SendEmailResponse"
        reqId <- response .: "ResponseMetadata" >>= (.: "RequestId")
        msgId <- response .: "SendEmailResult" >>= (.: "MessageId")
        return $ SendEmailResponse reqId msgId
    parseJSON _ = mzero

To make an actual call to SES we use our AWS access key ID and secret access key to construct the appropriate SendEmailRequest. We then pass that request to the sendEmail function.

main :: IO ()
main = do
    awsId <- C.pack <$> getEnv "AWS_ACCESS_KEY_ID"
    awsSecret <- C.pack <$> getEnv "AWS_SECRET_ACCESS_KEY"
    let sendRequest = SendEmailRequest usEast1 awsId awsSecret ""
                        [""] "Sent from Haskell"
                        "This email was sent through SES using Haskell!"
    response <- sendEmail sendRequest
    case response of
        Left err -> putStrLn $ "Failed to send : " ++ err
        Right resp ->
            putStrLn $ "Successfully sent with message ID : " ++ unpack (messageId resp)

Now we have a minimal working example to call SES from Haskell! Happy sending!

SPF and Amazon SES

by Adrian Hamciuc | on | Permalink | Comments |  Share

Update (3/14/16): To increase your SPF authentication options, Amazon SES now enables you to use your own MAIL FROM domain. For more information, see Authenticating Email with SPF in Amazon SES.

One of the most important aspects of email communication today is making sure the right person sent you the right message. There are several standards in place to address various aspects of securing email sending; one of the most commonly known is SPF (the short form of Sender Policy Framework). In this blog post we explain what SPF is, how it works, and how Amazon SES handles it. We also address the most common questions we see from customers with regard to their concerns around email security.

What is SPF?

Described in RFC 7208 ( ), SPF is an open standard designed to prevent sender address forgery. In particular, it is used to confirm that the IP address from which an email originates is allowed to send emails by the owner of the domain that sent the email. What does that mean and how does it happen?

Before going into how SPF works, we should clarify exactly what it does and does not do. First, let’s separate the actual email message body and its headers from the SMTP protocol used to send it. SPF works by authenticating the IP address that originated the SMTP connection to the domain used in the SMTP MAIL-FROM and/or the HELO/EHLO command. The From header, which is part of the email message itself, is not covered by SPF validation. A separate standard, DomainKeys Identified Mail (DKIM), is used to authenticate the message body and headers against the From header domain (which can be different from the domain used in the SMTP MAIL-FROM command).

Now that we’ve talked about what SPF does, let’s look at how it actually works. SPF involves publishing a DNS record in the domain that wants to allow IP addresses to send from it. This DNS record needs to contain either blocks of IP addresses that are permitted to send from it, or another domain to which authorization is delegated (or both). When an ISP receives an email and wants to validate that the IP address that sent the mail is allowed to send it on behalf of the sending domain, the ISP performs a DNS query against the SPF record. If such a record exists and contains the IP address in question or delegates to a domain that contains it, then we know that the IP address is authorized to send emails from that domain.

SPF and Amazon SES

If you are using Amazon SES to send from your domain, you need to know that the current SES implementation involves sending emails from an SES-owned MAIL-FROM domain. This means that you do not need to make any changes to your DNS records in order for your emails to pass SPF authentication.

Common concerns

There are a couple of questions we frequently hear from customers with regard to SPF authorization and how it relates to Amazon SES usage.

The first concern seems to be how your sending is affected if SPF authentication is performed against Amazon SES and not against your own domain.

If you’re wondering whether any other SES customer can send on your behalf, the answer is no. SES does not allow sending from a specific domain or email address until that domain or email address has been successfully verified with SES, a process that cannot take place without the consent of the domain/address’s owner.

The next question is whether you can still have a mechanism under your control that can authenticate the email-related content that you do control (things such as the message body, or various headers such as the From header, subject, or destinations). The answer is yes — Amazon SES offers DKIM signing capabilities (or the possibility to roll out your own). DKIM is another open standard that can authenticate the integrity of an email message, including its content and headers, and can prove to ISPs that your domain (not Amazon’s or someone else’s) takes responsibility and claims ownership of that specific email.

Another concern you may have is how much flexibility you get in using SPF to elicit a specific ISP response for unauthenticated or unauthorized emails from your domain. In particular, this concern translates into configuring DMARC (short for Domain-based Message Authentication, Reporting & Conformance) to work with SES. DMARC is a standard way of telling ISPs how to handle unauthenticated emails, and it’s based on both a) Successful SPF and/or DKIM authentication and b) Domain alignment (all authenticated domains must match). As explained above, your MAIL-FROM domain is currently an SES domain, which doesn’t match your sending domain (From header). As a result, SPF authentication will be misaligned for DMARC purposes. DKIM, on the other hand, provides the necessary domain alignment and ultimately satisfies DMARC because you are authenticating your From domain.

Simply put, you must enable DKIM signing for your verified domain in order to be able to successfully configure DMARC for your domain.

 If you have any questions, comments or other concerns related to SPF and your Amazon SES sending, don’t hesitate to jump on the Amazon SES Forum and let us know. Thank you for choosing SES and happy sending!

Bounces To Domains You Have Verified

by Samuel Minter | on | Permalink | Comments |  Share
Hello SES senders!  We have talked a number of times about how high bounce rates indicate a need to improve sending practices.  A high bounce rate can be a sign that someone is sending mail to lists that they have bought or rented, or that they aren’t maintaining their own lists, or a number of other problems.  As such, SES takes high bounce rates seriously, and requires all SES senders to keep their bounce rates low.
Over time though, we have noticed one type of bounce that, while still an indication that something is wrong, is usually not an indication that the user is doing anything that would cause a problem with ISPs. The type of bounce we’re referring to happens when a user sends some sort of notification to an invalid address at their own domain.
Sending notifications to an address at your own domain is certainly not a bad practice in itself. For example, you might use system notifications to alert you that there has been an error or something else that needs attention.  This works fine until one of the people receiving notifications leaves the company and their mail starts bouncing.  If significant sending to that address compared to your overall sending, your bounce rate may spike, despite your good intentions.
Another situation is people testing their system by sending mail to made-up addresses at their own domain.  This practice also causes bounce rates to skyrocket.
None of these bounces are something you want, of course.  In the first case, you should have set up bounce processing that would notice the new bounces and allow you to take action to change where the alerts go, or suppress them all together.  Otherwise, the notices aren’t serving their intended purpose anyway and are just wasting your resources. In the second case, you should be using the  SES mailbox simulator rather than sending to made-up addresses, because it is never good to intentionally send to addresses that don’t exist.
While it would be better if people didn’t bounce messages sent to their own domains, we recognize that this is a situation where SES can allow some flexibility.
Because of this, as of today, while bounces to domains you have  verified with us will still be sent to you as bounces and show up in your bounce metrics on the console and with GetSendStatistics, they will no longer "count" when we look at your bounce rate to determine if you have a bounce problem.  You should still fix the underlying issue, but we will not notify you of a problem if your bounce rate is only high because of messages you are sending to domains you have verified with us.
Note that we can’t let you off the hook for bounces to email addresses you have  individually verified though, because we need evidence that you in fact control the domain, and the domain owner won’t be upset with the volume of bounces hitting it.  If you do own the domain but have only used email address verification so far, consider verifying the entire domain.
As always, we encourage your feedback in the  SES forum. Thank you for using SES!