Domains

Add a sending domain, publish DKIM, SPF, and the ownership TXT record, and understand verification statuses and new-account limits

A verified domain lets you send from any address at that domain (hello@mail.example.com, receipts@mail.example.com, and so on). Verification proves two things: that Amazon SES can sign your mail with DKIM, and that you actually control the domain. Both are checked with DNS records you publish.

If you only need to send from one address and cannot edit DNS, see Senders instead.

Which domain to verify

Verify the exact domain that appears after the @ in your From address. Verifying example.com does not verify mail.example.com, and vice versa. A dedicated subdomain such as mail.example.com keeps your transactional reputation separate from the domain you use for day-to-day mail, and is what most teams choose.

DNS records

Adding a domain returns the records to publish. There are three kinds.

DKIM: three CNAME records

NameTypeValue
<token1>._domainkey.mail.example.comCNAME<token1>.dkim.amazonses.com
<token2>._domainkey.mail.example.comCNAME<token2>.dkim.amazonses.com
<token3>._domainkey.mail.example.comCNAME<token3>.dkim.amazonses.com

The tokens are unique to your domain and are in the API response. SES rotates signing keys through these CNAMEs, so leave them in place permanently.

SPF: one TXT record

NameTypeValue
mail.example.comTXTv=spf1 include:amazonses.com ~all

If the domain already has an SPF record, do not add a second one; SPF allows exactly one. Instead add include:amazonses.com to the existing record. Verification passes as long as a single TXT record at the domain contains both v=spf1 and include:amazonses.com, so v=spf1 include:_spf.google.com include:amazonses.com ~all is fine.

Ownership: one TXT record

NameTypeValue
_send-dev-verify.mail.example.comTXTsend-dev-verify=<token>

This record is how you prove to send.dev, not just to SES, that the domain is yours. The label is always _send-dev-verify regardless of which domain or subdomain you are verifying. The token is unique to the domain and is shown on the domain's page in the dashboard alongside the DKIM and SPF records.

send.dev does not require a DMARC record and does not check for one. DMARC is a receiver-side policy for your whole domain, and its reporting address must be a mailbox you actually read, so it is deliberately left in your hands. If you do not already publish one, a conservative starting point is:

NameTypeValue
_dmarc.example.comTXTv=DMARC1; p=none; rua=mailto:dmarc-reports@example.com

Replace the rua address with a mailbox you monitor, then tighten p= to quarantine or reject once you have confirmed that all your legitimate mail passes.

There is no custom MAIL FROM (bounce) domain to configure. Bounces are handled by send.dev and surface as suppressions.

Statuses

Each domain has two independent fields.

status (DNS verification)

ValueMeaning
pendingRecords not yet found, or found but SES has not finished confirming DKIM. The verify response says which.
verifyingAll records found; waiting on SES to confirm DKIM. Usually minutes, occasionally longer.
verifiedDKIM confirmed and ownership TXT found. The domain can send.
failedSES reported a DKIM failure, or the SES identity was removed. Re-check the CNAME values character for character.

approvalStatus (abuse check)

There is no manual approval step. A domain is approved automatically the moment its DNS records and ownership TXT verify. approvalStatus exists to surface the two exceptions.

ValueMeaning
approvedSending is allowed. Every clean domain lands here as soon as status becomes verified.
pending_approvalEither the ownership TXT record has not verified yet, or the domain tripped an automated abuse check (most often a name that closely resembles a well-known brand) and is held for a person to look at. You receive an email when a held domain is decided.
rejectedSending from this domain is blocked. The email you receive explains why; reply to it or contact support if you believe it is a mistake.

While a domain is pending_approval or rejected, sends from it return 403 with code sender_not_verified.

Sending limits for new accounts

Instead of reviewing domains up front, send.dev limits how fast a new account can send and watches how the first messages land. Every new account starts in a 72-hour probation:

StandardHigher-risk signup
Emails per hour5020
Emails per day200100
Distinct recipient domains per hour2510
Probation length72 hours7 days

Exceeding a cap returns 429 probation_limit_reached with limit, window, metric, and resetsAt (plus a Retry-After header); nothing is queued and the attempt does not count against your reputation. Probation is followed by an established stage (2,000 emails per day per domain) and then trusted (50,000 per day) after clean sending windows. Details and the full table are on Rate limits.

During probation, sending is paused automatically (403 sending_paused) if the early signals look like abuse: three bounces within the first 25 emails, any spam complaint within the first 100, a sudden spike well above your normal hourly rate, or repeated risky content (links to domains other than your own, URL shorteners, "account security" subject lines). A pause restarts the probation clock; reply to the notification email to get it lifted.

You can see where your account stands:

GET/v1/send/tenants

Return your account's sending status and trust stage.

Requires:send:read
{
"id": "ten_2m8x9q4wzk17c",
"name": "Example Store",
"sendingStatus": "ENABLED",
"trustStage": "probation",
"probationEndsAt": 1757259600000,
"createdAt": 1757000000000
}

trustStage is probation, established, or trusted. probationEndsAt (Unix milliseconds) is present only while on probation and is the earliest the caps can lift; they lift at that time if the account's 24-hour bounce rate is under 2% and it has had no complaints.

Daily sending limits per domain

Independently of the account-level probation, each domain has a daily cap that grows with its track record:

TierDaily limitPromoted after
New200 / dayVerification
Warming2,000 / dayAt least 100 sent, 3 days old, bounce rate under 1%, complaint rate under 0.05%
Trusted50,000 / dayAt least 5,000 sent, 14 days old, bounce rate under 0.5%, complaint rate under 0.03%

This is abuse protection, not pricing. It exists so that a compromised account or a bad list cannot damage deliverability for everyone on the platform, and it also happens to be how mailbox providers expect a new sending domain to ramp up. If you have a launch that needs more volume than the ladder allows, contact support with the domain and expected daily volume before the launch. Your plan's monthly limit applies on top of this.

Endpoints

List domains

GET/v1/send/domains

List every domain on the account with its DNS records and statuses.

Requires:send:read

Query parameters

statusstring
Only return domains with this verification status
pendingverifyingverifiedfailed
{
"domains": [
  {
    "id": "dom_k17c2m8x9q4wz",
    "domain": "mail.example.com",
    "status": "verified",
    "approvalStatus": "approved",
    "dkimStatus": "success",
    "isActive": true,
    "createdAt": 1756900000000,
    "verifiedAt": 1756903600000,
    "dnsRecords": [
      { "type": "CNAME", "name": "abc123._domainkey.mail.example.com", "value": "abc123.dkim.amazonses.com", "purpose": "dkim", "verified": true },
      { "type": "CNAME", "name": "def456._domainkey.mail.example.com", "value": "def456.dkim.amazonses.com", "purpose": "dkim", "verified": true },
      { "type": "CNAME", "name": "ghi789._domainkey.mail.example.com", "value": "ghi789.dkim.amazonses.com", "purpose": "dkim", "verified": true },
      { "type": "TXT", "name": "mail.example.com", "value": "v=spf1 include:amazonses.com ~all", "purpose": "spf", "verified": true },
      { "type": "TXT", "name": "_send-dev-verify.mail.example.com", "value": "send-dev-verify=7f3a9c...", "purpose": "ownership", "verified": true }
    ]
  }
],
"stats": { "total": 1, "verified": 1, "pending": 0, "failed": 0 }
}

Add a domain

POST/v1/send/domains

Register a domain and receive the DNS records to publish.

Requires:send:write

Body parameters

domainstringrequired
The domain or subdomain you will send from, for example mail.example.com. Lowercased and trimmed.
Add a domain
curl -X POST https://api.do.dev/v1/send/domains \
-H "Authorization: Bearer $SEND_API_KEY" \
-H "Content-Type: application/json" \
-d '{"domain": "mail.example.com"}'
Response201
{
"id": "dom_k17c2m8x9q4wz",
"domain": "mail.example.com",
"status": "pending",
"approvalStatus": "pending_approval",
"dkimStatus": "pending",
"dnsRecords": [
  { "type": "CNAME", "name": "abc123._domainkey.mail.example.com", "value": "abc123.dkim.amazonses.com", "purpose": "dkim" },
  { "type": "CNAME", "name": "def456._domainkey.mail.example.com", "value": "def456.dkim.amazonses.com", "purpose": "dkim" },
  { "type": "CNAME", "name": "ghi789._domainkey.mail.example.com", "value": "ghi789.dkim.amazonses.com", "purpose": "dkim" },
  { "type": "TXT", "name": "mail.example.com", "value": "v=spf1 include:amazonses.com ~all", "purpose": "spf" },
  { "type": "TXT", "name": "_send-dev-verify.mail.example.com", "value": "send-dev-verify=7f3a9c...", "purpose": "ownership" }
]
}

Verify a domain

POST/v1/send/domains/{id}/verify

Look up the DNS records now and update the domain's status. Call it after publishing records, and again if it reports something missing.

Requires:send:write

The response lists each record and whether it was found. Calling this endpoint repeatedly is fine; a reasonable pattern is once a minute for up to an hour after publishing.

Verify
curl -X POST https://api.do.dev/v1/send/domains/dom_k17c2m8x9q4wz/verify \
-H "Authorization: Bearer $SEND_API_KEY"
Response200
{
"id": "dom_k17c2m8x9q4wz",
"domain": "mail.example.com",
"status": "pending",
"approvalStatus": "pending_approval",
"dkimStatus": "pending",
"message": "Missing DNS records: SPF (TXT). Add these records in your DNS provider.",
"dnsRecords": [
  { "type": "CNAME", "name": "abc123._domainkey.mail.example.com", "value": "abc123.dkim.amazonses.com", "purpose": "dkim", "verified": true },
  { "type": "CNAME", "name": "def456._domainkey.mail.example.com", "value": "def456.dkim.amazonses.com", "purpose": "dkim", "verified": true },
  { "type": "CNAME", "name": "ghi789._domainkey.mail.example.com", "value": "ghi789.dkim.amazonses.com", "purpose": "dkim", "verified": true },
  { "type": "TXT", "name": "mail.example.com", "value": "v=spf1 include:amazonses.com ~all", "purpose": "spf", "verified": false },
  { "type": "TXT", "name": "_send-dev-verify.mail.example.com", "value": "send-dev-verify=7f3a9c...", "purpose": "ownership", "verified": true }
],
"pendingRecords": [
  { "type": "TXT", "name": "mail.example.com", "value": "v=spf1 include:amazonses.com ~all", "purpose": "spf" }
]
}

When everything checks out, status is verified, verifiedAt is set, approvalStatus flips to approved, and message reads Domain is fully verified and ready to send emails. You can send immediately, within the new-account limits. If approvalStatus is still pending_approval at that point, the domain tripped an abuse check and is being looked at (see above).

Delete a domain

DELETE/v1/send/domains/{id}

Remove the domain and its SES identity. Sends from the domain fail immediately with sender_not_verified.

Requires:send:write
{
"id": "dom_k17c2m8x9q4wz",
"domain": "mail.example.com",
"deleted": true
}

Deleting a domain does not remove the DNS records you published; clean those up yourself if you will not re-add the domain.

Troubleshooting

Records published but still pending. Check the dnsRecords[].verified flags in the verify response to see which record is missing. Common causes: a CNAME entered with the full hostname where the provider only wanted the label (so the record became abc123._domainkey.mail.example.com.mail.example.com), a trailing period issue, or a provider that "flattens" CNAMEs into A records (Cloudflare does this only when the record is proxied; set DKIM CNAMEs to DNS-only).

All records found, still pending or verifying. SES confirms DKIM on its own schedule. Minutes is normal; it can occasionally take hours. Nothing further is needed from you.

status is verified but sends return sender_not_verified. Look at approvalStatus. If it is pending_approval, the domain tripped an abuse check and is held for review; you will receive an email. If it is rejected, sending is blocked.

Sends return 429 probation_limit_reached. The domain is fine; your account is new and within its probation caps. Wait until resetsAt.

Two SPF records. SPF permits exactly one v=spf1 TXT record per name. Merge include:amazonses.com into your existing record.