Domains, sender profiles & warmup
Every message is sent from a sending domain: a domain or subdomain you register, connect to a mail provider, and prove you control by publishing DNS records. The From address of each message combines a sender profile — the mailbox part, display name and Reply-To — with the domain the message is sent from. A newly verified domain then follows a warmup schedule that raises its daily volume step by step, so mailbox providers learn to trust it instead of throttling a sudden burst of unfamiliar mail.
Sending domains
Key fields
| Field | Description |
|---|---|
id / domain |
Identifier and the registered name, for example mail.example.com. |
provider |
The mail provider the domain is connected to: ses (Amazon SES) or kumomta (KumoMTA). Set at registration and changed only with POST /domains/{id}/provider. It decides which DNS records you publish. |
status |
See Domain status. |
dns_records |
The records to publish, each with record (SPF, DKIM, DMARC, MAIL_FROM, VERIFY), DNS type, host, value, priority for MX, and mandatory. See DNS records. |
dkim_selector |
null for ses domains, whose DKIM keys are named by Amazon SES tokens. For kumomta, the selector the platform generated for the domain’s DKIM key. |
warmup_enabled |
Whether the domain follows the warmup schedule; true unless you set it to false at registration. |
verified_at |
When the last successful verification happened; null before that and after a provider change. |
reputation_verdict / reputation_reasons / reputation_checked_at |
The result of the reputation check made at registration. See Registration. |
Registration
POST /domains registers a domain and returns it with its DNS records, in status pending_dns.
Provider. GET /me lists the providers your account may use in allowed_providers:
- one provider —
provideris optional and that provider is used; - several providers —
provideris required, otherwise422 provider_required; - a provider outside the list —
403 provider_not_allowed; - an empty list — no domain can be connected yet,
422 no_provider_allowed.
Reputation check. Each new domain is checked against domain age and public blocklists. pass means every check completed cleanly; warn means a warning was recorded (for example, a very recently registered domain) and the domain is registered anyway; skipped means the checks could not all be completed and the domain is registered anyway. reject refuses the registration with 422 and details.code: domain_reputation_rejected, listing reason codes in details.reasons[{check, code}]; nothing is saved. Public mailbox and disposable-mail domains are refused the same way.
Uniqueness. A name can be registered once per account at a time; registering it again while it exists is 409 domain_already_registered. Only one account at a time can hold a given name in verified.
DNS records
Publish every record exactly as returned by GET /domains/{id}. All records are returned with mandatory: true. Each registration gets its own ownership token, so the VERIFY value changes if you delete and re-register a domain.
For a domain on ses — 8 records:
record |
DNS type | Host | Value |
|---|---|---|---|
SPF |
TXT | <domain> |
v=spf1 include:amazonses.com ~all |
DKIM |
CNAME ×3 | <token>._domainkey.<domain>, one per token |
Amazon SES DKIM target |
DMARC |
TXT | _dmarc.<domain> |
v=DMARC1; p=none |
MAIL_FROM |
MX | bounce.<domain> |
Amazon SES feedback host, priority 10 |
SPF |
TXT | bounce.<domain> |
v=spf1 include:amazonses.com ~all, for the return path |
VERIFY |
TXT | _platform-verify.<domain> |
Your domain’s ownership token |
For a domain on kumomta — 4 records:
record |
DNS type | Host | Value |
|---|---|---|---|
MAIL_FROM |
CNAME | bounce.<domain> |
The platform’s bounce host. No other MX or TXT record may exist on this name. |
DKIM |
TXT | <dkim_selector>._domainkey.<domain> |
The public key of the domain’s DKIM key |
DMARC |
TXT | _dmarc.<domain> |
v=DMARC1; p=none |
VERIFY |
TXT | _platform-verify.<domain> |
Your domain’s ownership token |
The platform proposes DMARC p=none and never changes it or recommends a stricter policy; tightening it to quarantine or reject is your decision. For kumomta any valid v=DMARC1 record passes verification. For ses, verification relies on Amazon SES confirming the DKIM and MAIL FROM records plus the ownership record; publish the SPF and DMARC records anyway, because receiving servers use them to decide whether to trust your mail.
Verification
Verification runs only when you call POST /domains/{id}/verify; there is no automatic re-check and no expiry, so call it again after changing DNS. The response is { domain_id, status, ok, reasons }:
ok: true— the domain is nowverified,verified_atis set, and warmup starts ifwarmup_enabledistrue.ok: false—reasonslists what is missing, each entry with acodeand, where it applies, therecord. Apending_dnsdomain moves toverifying; a domain that is alreadyverifiedstaysverified.
Reason codes (the list can grow over time):
code |
Meaning |
|---|---|
ownership_not_verified |
The VERIFY TXT record is missing or has the wrong value. |
ses_not_verified_for_sending |
Amazon SES has not confirmed the domain’s DKIM records yet. |
ses_mail_from_not_verified |
Amazon SES has not confirmed the MAIL FROM records on bounce.<domain> yet. |
ses_bounce_node_occupied |
Another record on bounce.<domain> prevents the MAIL FROM records from working. |
kumo_bounce_not_configured |
The bounce.<domain> CNAME is missing or points elsewhere. |
kumo_bounce_node_occupied |
bounce.<domain> also has MX or TXT records; remove them. |
kumo_dkim_not_configured |
The DKIM TXT record is missing or does not match the domain’s key. |
kumo_dmarc_not_configured |
No valid DMARC record was found. |
dns_lookup_failed |
DNS could not be queried; try again later. |
provider_unavailable |
The provider could not be checked right now; try again later. |
Any other kumo_* code points to a problem on the platform side rather than in your DNS; contact support if it persists.
DNS changes can take time to propagate, and Amazon SES confirms records on its own schedule, so a first verify often fails with ses_* reasons; retry after a while.
Domain status
| Status | Meaning |
|---|---|
pending_dns |
Registered, or re-provisioned after a provider change; waiting for DNS records and a verify call. Cannot send. |
verifying |
At least one verify call ran and did not pass. Cannot send. Fix the reported records and verify again. |
verified |
Verification passed. The domain can send, within its warmup allowance. |
disabled |
The domain’s provider was removed from your account’s allowed providers. It cannot send, and POST /domains/{id}/verify answers 403 provider_not_allowed until the provider is allowed again; a successful verify then returns it to verified. |
failed |
Not currently assigned: verification does not expire, and a failed check leaves a domain in verifying. |
Messages already queued from a domain that stops being verified are not sent: they end in failed (for example with domain_not_verified or provider_not_allowed) and their credits are refunded.
Lifecycle
POST /domains ──► pending_dns
│ verify fails
▼
verifying
│ verify passes (also directly from pending_dns)
▼
verified ──provider removed from your account──► disabled
▲ │
└──────── verify passes, provider allowed again ──┘
provider removed from your account (any status) ──► disabled
POST /domains/{id}/provider (any status) ──► pending_dns
DELETE /domains/{id} (any status) ──► removed
- Changing provider.
POST /domains/{id}/providerreconnects the domain: a new DKIM key and new DNS records are issued, the domain returns topending_dns,verified_atis cleared, and warmup progress is discarded — after the next successful verify, warmup starts again at stage 1. Choosing the domain’s current provider is allowed and is how you rotate its DKIM key. Until you publish the new records and verify again, new sends from the domain are refused and already queued messages fail and are refunded. A second change request while one is still in progress is409 provider_change_in_progress. - Deleting.
DELETE /domains/{id}removes the domain and its keys. You can register the same name again later; it gets a newid, a new ownership token and DNS records, and a fresh warmup. Messages sent earlier stay linked to the old domain id.
Sender profiles
A sender profile defines the mailbox a message is sent from. It stores only the part before the @; the domain comes from the message’s sending domain, so one profile works with all your verified domains.
Key fields
| Field | Description |
|---|---|
id |
The profile identifier. |
local_part |
The mailbox name, lower-cased, matching ^[a-z0-9](.?[a-z0-9_+-])*$, at most 64 characters, unique in your account. The From address is <local_part>@<sending domain>. It cannot be changed after creation. |
display_name |
The From display name, for example Acme; null for none. |
reply_to |
The Reply-To address; null for none. |
is_default |
Whether this is your default profile, used by every send type that has no route. Exactly one profile is the default. |
Every account starts with a default profile whose local_part is noreply, so messages go out as noreply@<sending domain> until you configure otherwise.
Routes
A route assigns a profile to one send type: blast, automation, transactional or otp. GET /senders/routes lists them; PUT /senders/routes replaces the whole table at once (at most one entry per type; duplicates are 422). A type without a route uses the default profile.
How the From address is chosen
The sender is resolved when the message is created, and the resolved address, display name and Reply-To are fixed on the message; editing a profile later does not change messages already created.
| Send path | Mailbox | Display name and Reply-To |
|---|---|---|
POST /transactional/send, POST /transactional/send-batch, POST /otp/request |
from, if given; else the route for transactional or otp; else the default profile |
From the resolved profile; the request’s reply_to overrides the Reply-To |
Campaign (blast) and automation |
The campaign’s sender_profile_id, if set; else the route for blast or automation; else the default profile |
From the resolved profile; the campaign’s from_name and reply_to override them |
POST /templates/{id}/test-send |
The route for blast for a marketing template, for transactional otherwise; else the default profile |
From the resolved profile |
On the transactional and OTP endpoints, from (local@domain or Name <local@domain>) also selects the sending domain, so either domain_id or from must be present. Its domain must be one of your verified domains, and its local part must be the local_part of one of your profiles.
Errors
| Code | Status | When |
|---|---|---|
sender_already_exists |
409 | POST /senders with a local_part you already use. |
sender_in_use |
409 | DELETE /senders/{id} while a route or a campaign still uses the profile. |
default_sender_undeletable |
409 | DELETE /senders/{id} on the default profile; make another profile the default first. |
sender_default_conflict |
409 | Two concurrent requests tried to make different profiles the default; retry. |
sender_not_found |
422 | A sender_profile_id (in a campaign or a route) that is not one of your profiles. |
sender_not_configured |
422 | No profile resolves for a send, for example a from whose local part matches none of your profiles. |
invalid_from_address |
422 | from is not local@domain or Name <local@domain>. |
from_domain_mismatch |
422 | The domain of from is not one of your verified domains. |
domain_required |
422 | Neither domain_id nor from was given. |
Lifecycle
POST /senders creates a profile, optionally as the new default. PATCH /senders/{id} changes display_name and reply_to, or promotes the profile to default with is_default: true (the previous default stops being the default; false is not accepted — promote another profile instead). DELETE /senders/{id} deletes a profile that is neither the default nor in use.
Warmup
Warmup limits how many messages a newly verified domain may send per day and raises that limit as the domain proves healthy. It starts when the domain is first verified with warmup_enabled: true. A domain with warmup_enabled: false has no daily warmup limit.
Warmup state
GET /domains/{id}/warmup returns the state, or payload: null when warmup is disabled for the domain or has not started yet (the domain was never verified).
| Field | Description |
|---|---|
domain_id / warmup_plan_id |
The domain and the warmup curve it follows. |
current_day |
The current stage of the curve (see below), not the number of days since verification. |
today_cap |
The daily limit of the current stage. |
today_cap_override |
Your own daily limit, if set; null otherwise. See Adjusting warmup. |
today_sent |
Messages handed to the provider today (UTC). |
state |
See the table below. |
state |
Meaning |
|---|---|
running |
The daily limit applies and the domain advances through the curve. |
holding |
The bounce or complaint rate of the current stage is above threshold, so the domain stays at its current stage; the daily limit still applies. |
paused |
You paused warmup with PATCH /domains/{id}/warmup. The stage, its accumulated volume and the day’s count are frozen until you resume. |
completed |
The curve is finished; the daily warmup limit no longer applies. |
The curve
| Stage | Daily limit |
|---|---|
| 1 | 50 |
| 2 | 100 |
| 3 | 500 |
| 4 | 1 000 |
| 5 | 5 000 |
| 6 | 10 000 |
| 7 | 25 000 |
| 8–9 | 50 000 |
| 10–11 | 100 000 |
| 12–13 | 200 000 |
| 14 | 400 000 |
| 15 | completed — no daily limit |
Progress is evaluated once a day at midnight UTC, and a domain moves at most one stage per day. Time alone never advances it:
- A stage advances only after the domain has sent the stage’s volume target (equal to its daily limit) since the stage began, and bounces and complaints on that volume stayed within threshold.
- A bounce rate above 5% or a complaint rate above 0.1% puts the domain in
holdingat its current stage. - A rate at least twice either threshold steps the domain back two stages, but never below stage 1, and restarts that stage’s volume count.
- After
completed, delivery health is still watched over the most recent days; a serious problem reopens warmup at stage 14, or at stage 13 for a severe one.
How the daily limit affects sending
The limit counts every message sent from the domain, whatever its class, and resets at midnight UTC. A message that would exceed it becomes deferred and is retried about once a minute. After 10 deferrals it becomes failed with defer_cap_exceeded and its credit is refunded; it is not held over to the next day. Plan large campaigns around the domain’s current stage: recipients beyond the day’s limit will fail rather than wait. See Messages.
Adjusting warmup
PATCH /domains/{id}/warmup accepts:
state: "paused"orstate: "running"— freeze or resume progression;today_cap_override— a daily limit of at least 1 that applies instead of the stage limit, until you clear it withnull. It changes only the enforced limit, not the stage’s volume target. When the domain steps back, an override higher than the new stage limit is cleared.
Before warmup has started, the call answers 404.
Related API
- Auth tag —
GET /me(allowed_providers). - Domains tag —
GET /domains,POST /domains,GET /domains/{id},DELETE /domains/{id},POST /domains/{id}/verify,POST /domains/{id}/provider,GET /domains/{id}/warmup,PATCH /domains/{id}/warmup. - Senders tag —
GET /senders,POST /senders,PATCH /senders/{id},DELETE /senders/{id},GET /senders/routes,PUT /senders/routes.
See the API Reference for full schemas, and Verify a sending domain for a worked example.