Sending
Verify a sending domain
This is the long-form companion to the dashboard’s “Set up domain” screen — the same records, explained once with room for every DNS provider’s own quirks. If you got here from that screen, it already opened you at your provider’s section below. If you are reading the API instead of the dashboard, start with Sending domains, which covers POST /v1/domains, the verify endpoint and the Cloudflare one-click integration — this page is deliberately just the walkthroughs.
What you’re adding
Verifying a domain proves you control its DNS. One record does that: DKIM. The other two are handed to you for completeness, and one of them is actively risky to paste over an existing value — read the warnings before you touch either.
| Record | Type | Name | Value |
|---|---|---|---|
| DKIM required | TXT | s1._domainkey.yourdomain.com | v=DKIM1; k=rsa; p=<a few hundred characters> |
| DMARC optional recommended | TXT | _dmarc.yourdomain.com | v=DMARC1; p=none; rua=mailto:[email protected] |
| SPF optional not needed | TXT | yourdomain.com | v=spf1 include:posthastemail.dev ~all |
DKIM is the only one that gates verification
We sign every message as your domain, and DMARC passes when either SPF or DKIM aligns — so DKIM alignment alone is sufficient, and it is the only check required: true on the verify response. Publish this one and the domain goes verified whether or not the other two are there.
The value is longer than one DNS string, and this is the single most common way verification gets stuck. A TXT record’s individual character-string is capped at 255 characters; the DKIM value is a public key and routinely runs longer. Providers handle this two different ways: some (Cloudflare, Vercel) accept the whole value pasted into one field and split it into multiple strings for you; others (AWS Route 53, and several smaller panels) need each chunk pasted as its own quoted string inside the same record — not as two separate TXT records at the same name. The dashboard’s copy button for this record includes the pre-split chunks for exactly this reason. The per-provider sections below say which case you’re in.
DMARC — only if you have none already
Your own policy for your own domain. Start at p=none and read at least two weeks of aggregate reports before tightening it — going straight to p=reject silently blackholes legitimate mail that has not yet been proven to align.
Only add this record if the domain has no DMARC record already. A name can only usefully carry one; if you publish a second, most validators just pick one of them arbitrarily, which is worse than either alone. Check first — dig TXT _dmarc.yourdomain.com +short — and if something is already there, keep yours and skip this row.
SPF — listed for completeness, not needed for our mail
Our envelope sender is our own bounce domain, so SPF is evaluated against us, not you, for anything we send. Your SPF record is never consulted.
Never paste this over an existing SPF record. A domain may publish only one, and replacing yours breaks every other system that sends as you — your CRM, your invoicing, your helpdesk. Verification never checks SPF and never fails because of it.
The steps that are the same everywhere
Every provider below is a variation on the same five steps. If your host is not on the list, or the layout has moved since this was written, this is still the whole procedure:
- Find where this domain’s DNS is actually managed. That is not always where you registered the domain — it is wherever its nameservers point, which can be a different company entirely.
- Add a new record of type TXT.
- Watch the host/name field. Most providers want only the subdomain part —
s1._domainkey, nots1._domainkey.yourdomain.com— because they append your domain automatically. Paste the full name and you gets1._domainkey.yourdomain.com.yourdomain.com, a record that exists and will never be found by anything looking for the real one. This is the second most common way verification gets stuck, right behind the chunking issue above. - Leave the TTL on its default (Automatic, or 3600/1 hour — the exact number rarely matters). Verification reads your domain’s authoritative nameservers directly, not a cache, so a low or high TTL here does not change how soon it can see the record — it only affects how soon other resolvers on the internet do.
- Save, then go back to the dashboard. It polls on its own every few seconds while the setup screen is open — you do not have to time anything — or press Check now.
Propagation to the authoritative nameservers is typically a few minutes; it can take up to an hour or two on some providers. A not_found result right after saving is normal — the record you just added has to be visible from the outside first.
Cloudflare
If Cloudflare also manages sending for this domain, skip the manual steps below entirely — the one-click Cloudflare integration writes the DKIM record for you from a scoped API token, no copy-paste involved.
- dash.cloudflare.com → the account → the zone for your domain → DNS → Records.
- Add record → Type TXT.
- Name: the subdomain part only —
s1._domainkey. Cloudflare appends the zone name for you. - Content: paste the whole DKIM value as one string. Cloudflare accepts a TXT value over 255 characters in a single field and splits it into multiple strings internally — there is no chunk-by-chunk pasting to do here.
- TTL: Auto. Proxy status (the orange/grey cloud) is irrelevant to a TXT record — Cloudflare never proxies non-HTTP(S) record types, so there is nothing to switch off.
- Save. Repeat for
_dmarcif you want it, and skip the SPF row unless the domain has none.
AWS Route 53
- console.aws.amazon.com/route53/v2/hostedzones → the hosted zone for your domain.
- Create record.
- Record name: the subdomain part only —
s1._domainkey. Route 53 appends the zone and shows you the resulting full name before you save, so you can confirm it did not double. - Record type: TXT.
- Value: Route 53 requires every string inside a TXT record to be double-quoted, and a value over 255 characters has to be entered as multiple quoted strings back to back in the same record — one string per line, each in quotes:
"v=DKIM1; k=rsa; p=MIIBIjANBgkq…" "…rest of the key…AB"
Use the chunk boundaries the dashboard shows you. A single unquoted paste over 255 characters is rejected outright, not silently truncated — so this one fails loudly rather than going stuck. - TTL: the 300-second default is fine.
- Create records.
GoDaddy
- dcc.godaddy.com/control/<yourdomain>/dns — or Domain Portfolio → the domain → DNS → Manage DNS.
- Add → Type TXT.
- Host: the subdomain part only —
s1._domainkey. GoDaddy appends the domain the same way as everywhere else on this page; the bare-domain SPF row uses@. - TXT Value: paste the full DKIM value first. GoDaddy’s single-field limit for a TXT value has changed over the years and is worth checking against what you see — if the field rejects the paste or the value looks cut off after saving, add the value as multiple TXT records at the same host name, one chunk per record, using the chunk boundaries the dashboard shows.
- TTL: leave the default.
- Save.
Namecheap
- Domain List → the domain → Manage → Advanced DNS (ap.www.namecheap.com/domains/domaincontrolpanel/<yourdomain>/advancedns).
- Add New Record → TXT Record.
- Host: the subdomain part only —
s1._domainkey. Namecheap appends the domain automatically. - Value: paste the full DKIM value. If Namecheap’s field errors on the length, split it at the dashboard’s chunk boundaries and add each chunk as its own TXT record at the same host.
- TTL: Automatic.
- Click the green checkmark to save — Namecheap batches edits in the row and does not persist them until you confirm that row.
Squarespace (including migrated Google Domains)
Google Domains was sold to Squarespace in 2023. If you registered through Google Domains, this is where your DNS lives now, not the old Google console.
- account.squarespace.com/domains → the domain → DNS Settings (labelled “DNS records” on some accounts).
- Add record → Type TXT.
- Host: the subdomain part only —
s1._domainkey. - Data/Value: paste the full DKIM value. If it is rejected for length, split it at the dashboard’s chunk boundaries into separate TXT records at the same host.
- TTL: default.
- Save.
Hostinger
- hpanel.hostinger.com/domain/<yourdomain>/dns — or Domains → the domain → DNS / Nameservers → DNS Records.
- Add Record → Type TXT.
- Name/Host: the subdomain part only —
s1._domainkey. Hostinger appends the domain. - Content: paste the full DKIM value. If it comes back truncated, add the chunked values as separate records at the identical host.
- TTL: default.
- Save Record.
Porkbun
- porkbun.com/account/domainsSpeedy → the domain → the DNS/edit icon opens the records screen.
- Add a record with type TXT from the type dropdown.
- Host: the subdomain part only —
s1._domainkey. Porkbun’s Host field works the same way as the rest — relative to the domain, not the full name. - Answer/Content: paste the full DKIM value; if the field balks at the length, split it at the dashboard’s chunk boundaries into separate TXT records at the same host.
- TTL: default.
- Save.
Vercel
Applies to domains added under a Vercel project or team, where Vercel is the DNS host (not every domain pointed at a Vercel deployment is also managed by Vercel’s DNS — check the Nameservers tab if you are not sure).
- vercel.com/dashboard/domains → the domain → DNS Records tab.
- Add → Type TXT.
- Name: the subdomain part only —
s1._domainkey. Vercel appends the domain and shows the resulting full name before you confirm. - Value: paste the full DKIM value — Vercel accepts the long string in one field.
- TTL: default (or leave blank).
- Add Record.
Everywhere else
Same five steps, briefly — where DNS lives, and the host-field convention. All of these append your domain to whatever you type in the host/name field, so enter only the subdomain part everywhere on this list, exactly as above.
Netlify
For a domain on Netlify DNS: Team → Domains → app.netlify.com/teams/-/dns. Add a TXT record; Host takes the subdomain part only.
DigitalOcean
cloud.digitalocean.com/networking/domains/<yourdomain> → the domain’s records tab. Add a TXT record; the Hostname field takes the subdomain part, or @ for the bare-domain SPF row.
Google Domains was sold to Squarespace in 2023 — if this domain was registered there, its DNS is now at Squarespace; see the Squarespace section above. If instead this domain uses Google Cloud DNS (a zone in a GCP project, unrelated to domain registration), manage it at console.cloud.google.com/net-services/dns/zones and add the TXT record set with the resource name set to the subdomain part.
Azure DNS
portal.azure.com → DNS zones → your zone → + Record set. Type TXT, Name set to the subdomain part only. Azure record sets accept multiple TXT value strings, the same “quoted strings in one record” shape as Route 53 above, for the long DKIM value.
IONOS
my.ionos.com/domain-overview → the domain → DNS (or Domains & SSL → DNS). Add a TXT record with the subdomain part only in the Host Name field.
OVH
OVH Manager → Web Cloud → Domains → the domain → DNS zone (ovh.com/manager/#/web/domain/<yourdomain>/zone). Add an entry of type TXT and enter only the subdomain part in the Sub-domain field — leave it blank for the bare-domain SPF row.
Gandi
admin.gandi.net/domain/<yourdomain>/records → Add a new record, type TXT, Name set to the subdomain part only.
Akamai / Linode
cloud.linode.com/domains → the domain → its DNS records list. Add a TXT Record with the Hostname field set to the subdomain part only — Linode (now part of Akamai) appends the domain the same way as the rest of this page.
Still not verifying
Still pending after an hour
Verification checks your domain’s authoritative nameservers, not a local or public resolver — so a stale cache on your laptop is never the reason it is stuck. Ask the authoritative server directly and see what it actually has:
# Find your domain's own nameservers
dig NS example.com +short
# ns1.provider.com.
# ns2.provider.com.
# Ask ONE of them directly, bypassing your local resolver's cache
dig TXT s1._domainkey.example.com +short @ns1.provider.com
# "v=DKIM1; k=rsa; p=MIIBIjANBgkqhki..."
# Empty here means the record was never actually saved — propagation is
# not the problem. See "double-domain" and "chunk" below.An empty answer here means the record was never saved the way you think it was — almost always one of the two mistakes below, not propagation.
The double-domain host mistake
The single most common failure. If you pasted the full name (s1._domainkey.yourdomain.com) into a Host/Name field that appends the domain for you, the record exists — just at the wrong name:
dig TXT s1._domainkey.example.com.example.com +short @ns1.provider.com
# "v=DKIM1; k=rsa; p=MIIBIjANBgkqhki..."
#
# A record answering HERE, at the doubled name, means the Host/Name field
# got the full name instead of just the subdomain part.If that query answers and the plain one above does not, you have found it — edit the record and enter only s1._domainkey in the host field.
The chunk pasted as two records instead of one
On a provider that needs the DKIM value split (Route 53, Azure, and a few smaller panels — see the per-provider notes above), each chunk has to be a quoted string inside one TXT record, not two separate TXT records at the same name. DNS does not define an ordering between two records at the same name and type, so a resolver may return either one, both concatenated in an unpredictable order, or just the first it sees — none of which matches the key we hold, so it always fails verification even though something is clearly published. Delete the duplicate and re-add it as one record with both quoted strings.
“DMARC already exists” / it’s marked not_found
DMARC is optional and never gates verification — a not_found or fail against _dmarc is not why the domain is stuck. If you were trying to add it and already had one, that is expected and correct: keep the existing record rather than adding a second.
NextSend an email →Every field on POST /v1/emails, including replyTo and List-Unsubscribe.