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 — provider is optional and that provider is used;
  • several providers — provider is required, otherwise 422 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 now verified, verified_at is set, and warmup starts if warmup_enabled is true.
  • ok: false — reasons lists what is missing, each entry with a code and, where it applies, the record. A pending_dns domain moves to verifying; a domain that is already verified stays verified.

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}/provider reconnects the domain: a new DKIM key and new DNS records are issued, the domain returns to pending_dns, verified_at is 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 is 409 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 new id, 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 holding at 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" or state: "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 with null. 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.

  • 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.