Skip to content

Amazon SES

Amazon Simple Email Service (SES) is the cheapest way to send large volumes of transactional email, and a natural choice if you already run on AWS. It needs a bit more setup than other providers – IAM, a region, leaving the sandbox – but after that it is very reliable.

Choose SES when you send thousands of emails a month or more, care about price per message, or want everything inside your own AWS account. If you want the quickest possible setup, Postmark, Resend or Mailgun are easier to start with. See the services overview.

  • An AWS account with SES enabled in the region you want to send from.
  • A verified identity in that region: preferably a whole domain (SES → Identities → Create identity → Domain), or at least the single From address.
  • Production access (leaving the sandbox) before you send to arbitrary recipients – see Sandbox & limits.
  • A dedicated IAM user with an access key and only these permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["ses:SendEmail", "ses:SendRawEmail", "ses:GetAccount"],
"Resource": "*"
}
]
}

ses:SendEmail / ses:SendRawEmail are needed for sending (Frontmail uses the SES v2 API with a raw MIME message), ses:GetAccount for Test connection and the daily health check, which also reads your quota and sandbox status. You can narrow Resource to your identity and configuration set ARNs. Do not use root credentials or an admin user.

  1. Open Email services → Add service → Amazon SES.
  2. Fill in the fields:
Field Where to find it Example
AWS region The region where your identity is verified (top-right of the SES console) eu-central-1
Access key ID IAM → Users → frontmail-ses → Security credentials → Create access key AKIAIOSFODNN7EXAMPLE
Secret access key Shown once when the access key is created wJalrXUtnFEMI/K7MDENG/…
Configuration set SES → Configuration sets (needed for delivery events) frontmail
SNS topic ARN Optional; SNS → Topics – events from other topics are then ignored arn:aws:sns:eu-central-1:123456789012:frontmail-events
Default From email Optional; a verified identity in the selected region (an address on a verified domain, or a verified address). Used when the template has no From hello@example.com
Default From name Optional; display name for the default sender Acme
Max emails per second Optional; keep it at or below your SES maximum send rate 14
  1. Click Verify & save. Frontmail calls GetAccount and shows whether the account is still in the sandbox, its maximum send rate and 24-hour quota.
  2. The service is saved once the check passes. SES has no “account address”, so set a Default From email here or a From email in every template – otherwise sending fails with sender_not_configured (see Default sender). You can change it later on the service page without re-verifying.
  3. The access key is encrypted with AWS KMS envelope encryption; it is only decrypted inside the sender process.

Easy DKIM – when you create a domain identity, SES gives you three CNAME records:

abcdef1234._domainkey.example.com. CNAME abcdef1234.dkim.amazonses.com.
ghijkl5678._domainkey.example.com. CNAME ghijkl5678.dkim.amazonses.com.
mnopqr9012._domainkey.example.com. CNAME mnopqr9012.dkim.amazonses.com.

AWS rotates the keys behind them automatically. Once they resolve, the identity shows Verified.

SPF via a custom MAIL FROM domain – by default the envelope sender is an amazonses.com address, so SPF passes but does not align with your domain. For DMARC alignment on SPF too, set a custom MAIL FROM (e.g. bounce.example.com) on the identity and publish:

bounce.example.com. MX 10 feedback-smtp.eu-central-1.amazonses.com.
bounce.example.com. TXT "v=spf1 include:amazonses.com ~all"

(Replace eu-central-1 with your region.)

DMARC – start in monitoring mode, then tighten:

_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com"

SES is regional: identities, sandbox status, quotas, configuration sets and suppression lists are all per region. The region you pick in Frontmail must be the one where:

  • your domain or address is verified,
  • you requested production access,
  • your configuration set and SNS topic live.

The IAM access key itself is global, but using it against a region where nothing is set up fails with “Email address is not verified”. For EU data residency pick an EU region such as eu-central-1 (Frankfurt), eu-west-1 (Ireland) or eu-north-1 (Stockholm).

Every new SES account starts in the sandbox in each region:

  • you can send only to verified addresses or domains,
  • at most 200 messages per 24 hours,
  • at most 1 message per second.

Request production access in SES → Account dashboard → Request production access. Describe the use case (transactional email from a web form via Frontmail), how recipients opted in and how you handle bounces and complaints. Approval usually takes about a day.

After approval your quota starts at a modest level (e.g. 50,000/day, 14/s) and grows with good sending history. The current values are shown by Test connection; set Max emails per second at or below your maximum send rate.

SES publishes events through Amazon SNS. Frontmail subscribes to your SNS topic over HTTPS.

  1. SNS → Topics → Create topic (Standard), e.g. frontmail-events, in the same region.

  2. Create subscription → protocol HTTPS → endpoint = the webhook URL from the service page in Frontmail (copy button):

    https://api.frontmail.dev/webhooks/providers/ses/<token>

    Leave raw message delivery off. Frontmail verifies the SNS signature and confirms the subscription automatically – within a few seconds the subscription status changes from Pending confirmation to Confirmed.

  3. SES → Configuration sets → Create set (e.g. frontmail) → Event destinations → Add destination → choose event types → destination Amazon SNS → your topic.

  4. Put the configuration set name (and optionally the topic ARN) into the Frontmail service and save.

SES event type Frontmail event
Deliveries delivered
Hard bounces (Permanent) bounced – added to the suppression list
Soft bounces (Transient), delivery delays recorded on the message
Complaints complained – added to the suppression list
Opens opened
Clicks clicked
Rejects, rendering failures recorded on the message

Signature verification: every SNS message is signed. Frontmail downloads the signing certificate only from sns.<region>.amazonaws.com, verifies the signature (SignatureVersion 1 or 2) and rejects messages older than one hour. If you set SNS topic ARN, notifications from any other topic are rejected. If you leave it empty, Frontmail pins the topic of the first validly signed message (normally the subscription confirmation) and from then on rejects messages from any other topic – an SNS signature only proves that some AWS account sent the message.

Frontmail adds a frontmail_message_id message tag to each email so events can be matched back to the message.

  • auth – the service is broken (invalid/deleted access key, missing IAM permission, sending paused, sender not verified). The service is marked unhealthy and the owner gets an email plus a dashboard banner. With a fallback service on the template, the message goes through it; otherwise the credit is refunded and the message waits in the hold queue as held_service_error until you fix the service (re-checked on save and in the daily health check), then it is sent automatically.
  • permanent – this message cannot be sent (invalid or unverified recipient, rejected content). Fallback service if configured, otherwise failed with the credit refunded.
  • transient – throttling, timeouts, 5xx. Retried with exponential backoff; after the last retry failed and the credit refunded.
SES error Class What to do
InvalidClientTokenId / UnrecognizedClientException / SignatureDoesNotMatch auth The access key ID or secret is wrong or deleted. Create a new key for the IAM user.
AccessDeniedException: … not authorized to perform ses:SendRawEmail auth Add the missing action to the IAM policy (see above).
MessageRejected: Email address is not verified … hello@example.com (the sender) auth Verify the domain or From address in the selected region.
MailFromDomainNotVerifiedException auth Fix the custom MAIL FROM MX/TXT records or remove the custom MAIL FROM.
SendingPausedException / AccountSuspendedException auth AWS paused sending (often high bounce or complaint rate). Check SES → Reputation metrics and AWS notices.
MessageRejected: Email address is not verified … customer@gmail.com (a recipient) permanent The account is still in the sandbox – request production access or verify the recipient.
BadRequestException / ValidationException (invalid address) permanent See invalid recipient.
TooManyRequestsException / Throttling: Maximum sending rate exceeded transient Lower Max emails per second to your SES send rate; ask AWS for a higher quota.
InternalFailure, ServiceUnavailable, timeouts transient Nothing – Frontmail retries.
  • Domain identity verified with Easy DKIM (3 CNAMEs) in the chosen region
  • Custom MAIL FROM (MX + SPF) and DMARC p=none published
  • Production access granted in that region
  • IAM user with only ses:SendEmail, ses:SendRawEmail, ses:GetAccount
  • Test connection passes and shows sandbox: false
  • Configuration set → SNS topic → HTTPS subscription to the Frontmail webhook URL, status Confirmed
  • Configuration set name entered in Frontmail
  • Max emails per second at or below your SES send rate