---
title: "Bring your own AWS"
description: "Run the Mailmark email infrastructure inside your own AWS account with CloudFormation."
source: "https://www.mailmark.dev/docs/byo-aws"
site: "Mailmark"
---

# Bring your own AWS

Deploy the same SES / S3 / Lambda / SNS resources Mailmark uses, but inside your own AWS account, via a CloudFormation Quick-Create stack. Keep full ownership of your email data.

## When to use BYO-AWS

For most users the default Mailmark infrastructure option is simpler and recommended: Mailmark operates the AWS resources for you and you pay only the Mailmark subscription. Pick BYO-AWS when you need one of:

- **Data residency** - raw inbound email objects live in your own S3 bucket, in the AWS region you choose.
- **Your own SES reputation** - you already have a warmed-up SES account with production access approved.
- **Regulatory / compliance** - mailbox contents must be stored in an account you control.
- **Consolidated billing** - you prefer SES and S3 costs on your existing AWS bill.

You can mix and match: some domains on Mailmark infrastructure, others on BYO-AWS. The choice is per-domain.

## What gets provisioned

The CloudFormation stack creates the following resources in your AWS account:

- **SES configuration set** plus identity registration for your domain.
- **S3 bucket** for inbound email storage. Objects remain in your account.
- **Lambda forwarder** that picks up new S3 objects and POSTs them to the Mailmark inbound webhook, signed with a per-account shared secret.
- **SNS topics** for bounce, complaint, and delivery notifications, pointing to the Mailmark sending webhook.
- **IAM role** trusted by Mailmark's AWS account, with an `ExternalId` condition. Mailmark uses this role (via `STS.AssumeRole`) whenever it needs to call SES or S3 on your behalf.

The `ExternalId` is generated per Mailmark account and baked into the trust policy. It prevents the confused deputy problem: even if someone else learned your role ARN, they could not assume it without the ExternalId.

## Step-by-step setup

Two entry points to the connect wizard:

- Dashboard -> Domains -> Add domain -> Use my own AWS account -> Connect a new AWS account, or
- Dashboard -> Settings -> Connect AWS account.

The wizard has three steps:

1. **Alias & region.** Pick a friendly name for the account (e.g. Production) and the AWS region to provision in (e.g. `us-east-1`).
2. **Deploy the CloudFormation stack.** Click Open CloudFormation in AWS. The Quick-Create URL pre-fills every parameter: `ExternalId`, `MailmarkAwsAccountId`, `WebhookSecret`, `InboundWebhookUrl`, and `SendingWebhookUrl`. Sign in with the AWS account you want to use, review the parameters, acknowledge the IAM capabilities checkbox, and click Create stack.
3. **Paste the outputs.** When the stack status turns `CREATE_COMPLETE`, open the Outputs tab and copy `RoleArn` and `BucketName` into the Mailmark wizard, then click Verify.

On Verify, Mailmark calls `STS.AssumeRole` with your RoleArn and ExternalId, confirms identity with `STS.GetCallerIdentity`, and probes `SES.GetAccount` to detect whether your SES account is still in the sandbox. On success the account status flips to Verified and it becomes selectable when adding domains.

## Ongoing behavior

**SES sandbox.** Fresh AWS accounts start with SES in sandbox mode, which only allows sending to verified recipients. If Mailmark detects the sandbox, a SES sandbox badge is shown next to the account. Request production access in the AWS console to remove it.

**Multiple domains, one AWS account.** A connected AWS account can host any number of Mailmark domains. The Settings page lists every linked domain per account.

**Re-verification.** Mailmark caches the verification result. If you rotate the IAM role, delete and recreate the stack, or change the `ExternalId`, the next operation requiring AssumeRole fails and the account status flips to Failed with the AWS error message shown inline.

## Disconnecting an AWS account

Go to Dashboard -> Settings, find the account card, and click Disconnect -> Confirm disconnect.

Disconnect is blocked while any domain is still linked to the account. The Settings card lists the offending domains: delete them in Mailmark first (Dashboard -> Domains -> [domain] -> Delete), then retry.

Disconnecting only removes the link between Mailmark and your AWS account. The CloudFormation stack in your AWS account is not deleted by Mailmark. To free the resources, delete the stack yourself in the AWS console.

## Troubleshooting

**"AssumeRole failed" / "Could not assume role"**

- The role's trust policy does not allow Mailmark's AWS account as principal. Check that the `MailmarkAwsAccountId` stack parameter matches the value shown in the wizard.
- The `ExternalId` on the stack differs from the one Mailmark issued. Recreate the stack with the exact `ExternalId` from the wizard, or re-run the wizard for a fresh one.
- You pasted a different role's ARN. The wizard expects the role ARN listed under the stack's Outputs tab.

**"TemplateURL must be a supported URL" when clicking Open CloudFormation in AWS**

An operator-side configuration error: the Mailmark deployment is missing `MAILMARK_CFN_TEMPLATE_URL`, or it points to a non-S3 URL. CloudFormation only accepts template URLs hosted on S3. If you self-host Mailmark, upload `public/infra/byo-aws-cfn.yml` to a public-readable S3 object and set `MAILMARK_CFN_TEMPLATE_URL` to that URL in Convex. Otherwise contact Mailmark support.

**SES sandbox badge keeps showing after production access is granted**

Mailmark rechecks `SES.GetAccount` on the next verification. Reconnect from Settings, or wait for the next scheduled re-check.

## Next steps

- [Domain Setup](/docs/domain-setup) - DNS records are identical for Mailmark-hosted and BYO-AWS domains.
- [Troubleshooting](/docs/troubleshooting) - general fixes for domain verification and delivery issues.

---

Mailmark - email hosting and campaigns for your own domain. Canonical page: https://www.mailmark.dev/docs/byo-aws | [llms.txt](https://www.mailmark.dev/llms.txt) | [OpenAPI](https://www.mailmark.dev/openapi.json) | [sitemap](https://www.mailmark.dev/sitemap.xml)
