Skip to content

[13.x] Add SES tenant support to the SES v2 transport - #60886

Merged
taylorotwell merged 1 commit into
laravel:13.xfrom
atymic:feat/ses-tenant-awareness
Jul 26, 2026
Merged

[13.x] Add SES tenant support to the SES v2 transport#60886
taylorotwell merged 1 commit into
laravel:13.xfrom
atymic:feat/ses-tenant-awareness

Conversation

@atymic

@atymic atymic commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Amazon SES tenants let you isolate sending into separate containers, each with its own reputation metrics, reputation policy and suppression list. If one tenant's sending goes bad, SES pauses that tenant instead of the whole account. Useful if you send on behalf of a lot of downstream customers.

To send as a tenant you pass TenantName to SendEmail. The AWS SDK has supported this since 3.352.0, but there's no way to set it per message right now. SesV2Transport options are transport level, so a single ses mailer can't vary the tenant between messages.

This adds an X-SES-TENANT-NAME header that the transport turns into the TenantName parameter, same as the existing X-SES-LIST-MANAGEMENT-OPTIONS header does.

From a mailable:

public function headers(): Headers
{
    return new Headers(text: ['X-SES-TENANT-NAME' => 'tenant-id']);
}

Or a notification:

return (new MailMessage)->withSymfonyMessage(function ($message) {
    $message->getHeaders()->addTextHeader('X-SES-TENANT-NAME', 'tenant-id');
});

If the header isn't set, TenantName isn't passed and nothing changes.

Older SDK versions

Laravel requires aws/aws-sdk-php ^3.322.9, and TenantName only landed in 3.352.0, so I tested against 3.351.0 to check what happens on an SDK that predates it. It's safe: the SDK builds requests from its bundled API model, so an unknown key doesn't hit validation and is just left out of the request body. Nothing throws, the mail still sends, it just sends without a tenant.

I didn't bump the constraint, since that would push every SQS/DynamoDB/SES user onto a newer SDK for one optional SES feature. Happy to add a runtime exception when the installed SDK is too old if you'd prefer it fail loudly rather than silently ignore the header, just left it out for now to keep this small.

Notes

Setting the tenant in the options array in config/mail.php already works if you only have one tenant, so this is just for varying it per message.

The header stays on the outgoing message, which is the same as X-Metadata-* and X-SES-LIST-MANAGEMENT-OPTIONS today. Transports can't strip headers because SentMessage captures the raw MIME before doSend() runs.

I've used X-SES-TENANT-NAME to match the API parameter and the other X-SES-* headers, but SES uses X-SES-TENANT for its SMTP integration. Happy to rename it if you'd prefer to match that.

@atymic

atymic commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

Test failures seem unrelated, all email tests pass for me.

@taylorotwell
taylorotwell merged commit b79e2d6 into laravel:13.x Jul 26, 2026
23 of 55 checks passed
AmadulHaque added a commit to AmadulHaque/framework that referenced this pull request Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants