Skip to Content
🎉 Bref 3.0 is released. Read more →

Sending emails

Applications running on AWS Lambda can send emails with any email provider: Mailgun, Postmark, Resend, etc. are configured the same way as on a server. Bref does not require nor recommend a specific provider.

This page documents how to use Amazon SES  (Simple Email Service), the email service of AWS. Unlike other AWS services in this documentation, like SQS or S3, using SES is entirely optional.

Compared to other email providers, SES has some advantages on AWS Lambda:

  • No API key: the application sends emails with its IAM role, there is no secret to store.
  • Same AWS account as the application, billed per email ($0.10 per 1,000 emails ).

But SES does not list sent emails, bounces or complaints in a dashboard out of the box.

Verifying a domain

SES only sends emails from verified domains or addresses. Create the domain identity in the same region as your application.

Send emails from a subdomain, for example mail.example.com: its sending reputation is separate from the main domain, and the main domain keeps its MX records to receive emails.

Open the SES identities page  in the region of your application, and click “Create identity”:

  • select “Domain” and enter mail.example.com,
  • check “Use a custom MAIL FROM domain” and enter bounce (the MAIL FROM domain is then bounce.mail.example.com),
  • keep “Easy DKIM” selected.

The page of the identity then lists the DNS records to create.

Create the following DNS records. If you use Cloudflare, disable the proxy on the CNAME records (“DNS only”).

TypeNameValue
CNAME (×3)<token>._domainkey.mail.example.com<token>.dkim.amazonses.com
MX (priority 10)bounce.mail.example.comfeedback-smtp.<region>.amazonses.com
TXTbounce.mail.example.com"v=spf1 include:amazonses.com ~all"
TXT_dmarc.example.com"v=DMARC1; p=none"

The DMARC record applies to the main domain and all its subdomains. Skip it if your domain already has one. p=none only reports failures: once all the emails sent from your domain pass DMARC, you can switch to p=quarantine or p=reject.

SES verifies the domain a few minutes after the DNS records are created. Both statuses returned by this command should be SUCCESS:

aws sesv2 get-email-identity --region eu-west-3 --email-identity mail.example.com \ --query '{dkim: DkimAttributes.Status, mailFrom: MailFromAttributes.MailFromDomainStatus}'

Receiving replies

The mail.example.com subdomain has no MX record, so it cannot receive emails. Set a Reply-To address on a domain that receives emails, for example contact@example.com. See the Laravel example below.

Allowing the application to send emails

Allow the application to send emails from the domain identity in serverless.yml:

serverless.yml
provider: iam: role: statements: - Effect: Allow Action: - ses:SendEmail - ses:SendRawEmail Resource: arn:aws:ses:${aws:region}:${aws:accountId}:identity/mail.example.com # Optional: restrict the sender address Condition: StringEquals: ses:FromAddress: contact@mail.example.com

ses:SendRawEmail is required even with the SES v2 API: Laravel and Symfony send the full MIME message (raw content), which AWS checks as ses:SendRawEmail. Without it, sending fails with:

User: arn:aws:sts::…:assumed-role/… is not authorized to perform: ses:SendRawEmail on resource: arn:aws:ses:…:identity/mail.example.com

While the AWS account is in the SES sandbox, AWS also checks the identity of the recipient: the policy above denies emails sent to verified test addresses. Add these addresses to Resource during tests (for example arn:aws:ses:${aws:region}:${aws:accountId}:identity/test@example.com), and remove them once the account has production access.

Laravel

Laravel sends emails with SES through the AWS SDK:

composer require aws/aws-sdk-php

In config/mail.php, switch the transport of the ses mailer to ses-v2, to use the SES v2 API (the API AWS keeps evolving):

config/mail.php
'mailers' => [ // ... 'ses' => [ 'transport' => 'ses-v2', ], ],

Then select the ses mailer, and set the sender address, in serverless.yml:

serverless.yml
provider: environment: MAIL_MAILER: ses MAIL_FROM_ADDRESS: contact@mail.example.com MAIL_FROM_NAME: My application

There is no need to change the ses configuration in config/services.php: on AWS Lambda, Bref’s Laravel integration configures SES with the credentials of the Lambda function. The region is the region of the application.

To receive replies (see above), set a global Reply-To address in config/mail.php:

config/mail.php
'reply_to' => [ 'address' => env('MAIL_REPLY_TO_ADDRESS'), 'name' => env('MAIL_FROM_NAME'), ],

If your composer.json removes unused AWS services from the AWS SDK, add SesV2 to the services it keeps, then run composer install. Otherwise, sending emails fails in production with Class "Aws\SesV2\SesV2Client" not found.

composer.json
"extra": { "aws/aws-sdk-php": [ "Sqs", "SesV2" ] }

Tests and local development don’t catch this error if they install dependencies with composer install --no-scripts: the unused services are only removed when the scripts run.

Symfony

This Symfony configuration has not been tested yet. If it works for you, please send a pull request  that removes this warning to confirm it.

Install the Amazon SES transport for Symfony Mailer:

composer require symfony/amazon-mailer

Configure it in serverless.yml, without access key in the DSN: the transport then uses the credentials of the Lambda function.

serverless.yml
provider: environment: MAILER_DSN: ses+api://default?region=${aws:region}

Read more in the Symfony Mailer documentation .

Other PHP applications

Use the SesV2Client of the AWS SDK. On AWS Lambda, it uses the credentials and the region of the Lambda function:

$ses = new \Aws\SesV2\SesV2Client([]); $ses->sendEmail([ 'FromEmailAddress' => 'contact@mail.example.com', 'Destination' => [ 'ToAddresses' => ['john@example.com'], ], 'Content' => [ 'Simple' => [ 'Subject' => ['Data' => 'Hello'], 'Body' => [ 'Text' => ['Data' => 'Hello world!'], ], ], ], ]);

Leaving the SES sandbox

In each region, a new AWS account starts in the SES sandbox : it can send up to 200 emails per 24 hours, and only to verified addresses.

To send emails to anyone, open the SES account dashboard  in the region of your application, and click “Request production access”. The region must have a verified identity.

AWS usually replies within a day, often with questions about how you send emails. Answer all of them in one message to avoid back-and-forth:

  • the URL of the website: deploy the application on its real domain first, as reviewers look at it,
  • the type of emails (for example login links and notifications) and the expected volume,
  • how recipients sign up to receive them,
  • how bounces and complaints are handled: the account-level suppression list  (enabled by default) stops sending to addresses that bounced or complained,
  • an example email.
Last updated on