Open dashboard
Setup guideDKIM · SPF · DMARC

Verify your sender domain

Five-minute setup that takes your sends from "lands in spam" to "lands in inbox." Every step, every DNS provider, every gotcha — all in one page.

Why verify a domain#

Email is a trust system. Every receiver — Gmail, Outlook, Apple Mail, your corporate firewall — has to decide whether to deliver, route to spam, or reject. The signal they use is whether the sender owns the domain in the From header. Domain verification proves that.

Sendoka uses AWS SES DKIM for verification: when you add a domain, SES gives you three DNS records to add. Once they propagate, SES signs every outbound message with your domain's cryptographic key — receivers see the signature and trust the message.

Without verification: 403 DOMAIN_NOT_ALLOWED_FOR_KEY on send. Or, even if the domain exists in your account but the row is pending: SES rejects with MessageRejected: Email address is not verified.

Skip to the quickstart →

Before you start#

You need three things to verify a domain:

  • Owner-level access to your DNS provider (Cloudflare, Route 53, Namecheap, etc). You'll add 3 CNAME records.
  • An owner role on the Sendoka org (or a platform-root API key with write:domains scope).
  • ~5 min of patience. DNS propagation is usually instant on modern providers (Cloudflare, Route 53), but some legacy providers can take up to 24h.
Don't have a domain yet? Every Sendoka org gets sandbox.sendoka.com auto-provisioned for testing. Sends from that domain hit a sandbox inbox — fine for prototyping, not for production. Real customer email needs a real verified domain.

Quickstart (5 min)#

The fastest path. Add → copy 3 records → paste at DNS → verify.

1. Add the domain

In the dashboard or via API. We recommend a subdomain like mail.yourdomain.com over the root — see subdomain strategy.

Dashboard → Domains → Add domain
  Enter: mail.yourdomain.com
  Click "Add"

2. Copy the three CNAME records

The response (or dashboard) shows three DKIM tokens. Each becomes a CNAME pointing at amazonses.com:

text
Type   Host                                              Target
─────  ────────────────────────────────────────────────  ──────────────────────────────────
CNAME  <token1>._domainkey.mail.yourdomain.com           <token1>.dkim.amazonses.com
CNAME  <token2>._domainkey.mail.yourdomain.com           <token2>.dkim.amazonses.com
CNAME  <token3>._domainkey.mail.yourdomain.com           <token3>.dkim.amazonses.com

The exact tokens are unique to your domain — copy them from the dashboard or API response.

3. Add them at your DNS provider

Jump to your provider's specific recipe: Cloudflare · Route 53 · Vercel · Namecheap · GoDaddy · Google Domains.

4. Trigger verification

Most providers propagate in seconds. Click Re-check in the dashboard, or:

bash
curl -X POST https://api.sendoka.com/api/v1/domains/dom_xxx/verify \
  -H "Authorization: Bearer $SENDOKA_API_KEY"

Status flips to verified. You can now send from any address at that domain.

DKIM (required)#

DomainKeys Identified Mail. A cryptographic signature added to every outbound message header. Receivers fetch your public key from DNS, verify the signature, and now know the message wasn't forged or modified in transit.

Sendoka creates an SES "Email Identity" for your domain when you add it. SES generates three DKIM key pairs and gives you the public-key CNAME records to publish. The three records are not optional — SES rotates between them, so all three must resolve.

Records live at <token>._domainkey.<your-domain>. They are CNAMEs, not TXT — a common mistake. The target ends in .dkim.amazonses.com.

SPF (recommended)#

Sender Policy Framework. Lists the servers allowed to send mail "from" your domain. DKIM proves authenticity; SPF proves the path. Most receivers want both aligned for inbox placement.

Add a single TXT record at the root of your sending domain (or update an existing v=spf1 record by appending to it):

text
Type   Host                       Value
─────  ─────────────────────────  ───────────────────────────────────
TXT    mail.yourdomain.com        "v=spf1 include:amazonses.com ~all"

~all is "soft fail" — receivers may treat unaligned senders as suspicious. Use -all ("hard fail") only after you're confident every legitimate sender is in the include list.

If you already have an SPF record at the same host, append include:amazonses.com to the existing one — you can have only one SPF TXT per host.

DMARC (recommended)#

Domain-based Message Authentication. Tells receivers what to do when a message fails DKIM and SPF — quarantine, reject, or just report. DMARC also lets you receive aggregate reports about who's sending as you (legit and otherwise).

A starter "monitoring" policy:

text
Type   Host                                Value
─────  ──────────────────────────────────  ─────────────────────────────────────────────
TXT    _dmarc.mail.yourdomain.com          "v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com"

Start with p=none for 2–4 weeks while you read the aggregate reports. Then move to p=quarantine, then p=reject once nothing legitimate is failing.

MX (inbound only)#

MX records route incoming mail. You only need one if you want to receive email at this domain (e.g. catch replies for an auto-responder, run a help-desk inbox, etc).

For send-only domains, skip MX entirely. If you publish an MX record pointing at SES inbound, see the inbound messages guide.

Gmail / Google Workspace#

Most common confusion: “I'm using Gmail” can mean two different things, and only one of them works with Sendoka.

Personal @gmail.com address: you cannot send from you@gmail.com via Sendoka (or any third-party provider). Google does not allow third parties to DKIM-sign mail from gmail.com— only Google's own servers can. Use a custom domain, even a free one.
Custom domain on Google Workspace (e.g. you@yourcompany.comhosted by Google): this works fine. You add Sendoka's DKIM CNAMEs at your DNS provider — wherever you actually registered the domain. Google Workspace handles inbound mail (MX records); DNS for everything else lives at your registrar.

Step 1 — find where your DNS is hosted

Run this from any terminal:

bash
dig NS yourdomain.com +short
# Examples:
#   ns-cloud-a1.googledomains.com.   ← Google Domains (legacy, migrating to Squarespace)
#   adam.ns.cloudflare.com.          ← Cloudflare DNS
#   ns-1234.awsdns-...               ← Route 53
#   ns1.namecheap.com.               ← Namecheap

Whatever shows up is where you publish the DKIM CNAMEs. The provider guides below cover each common case. Workspace doesn't change this — having Workspace as your mail host doesn't mean your DNS is at Google.

Step 2 — publish the DKIM CNAMEs

Jump to the matching guide: Cloudflare · Google Domains / Squarespace · Route 53 · Namecheap · GoDaddy.

Step 3 — about Google Workspace's own DKIM

Workspace generates its own DKIM record (one TXT at google._domainkey.yourdomain.com) for mail sent from Workspace accounts. Sendoka's three CNAMEs are separate — they sit alongside Google's record at different host names. Both can coexist; they sign different mail. Don't replace one with the other.

Step 4 — required for sending to Gmail (Feb 2024+)

Google enforces rules for any sender hitting Gmail inboxes (still in effect 2026):

  • SPF AND DKIM required for every send. Sendoka handles DKIM; you publish the SPF TXT (SPF section).
  • DMARC required if you send more than ~5 000 messages/day to Gmail. Even p=none counts — see DMARC.
  • One-click unsubscribe (RFC 8058) for marketing / bulk mail. Sendoka injects this header automatically when track_opens or track_clicks is on.
  • Spam complaint rate < 0.3%, ideally < 0.1%. Watch the Deliverability card on the dashboard.

Failing any of these = your mail bounces or lands in spam at Gmail. The bar is real — Yahoo / Apple followed in 2024 with similar policies.

Step 5 — verify with Gmail's message viewer

Best confirmation: send a test from Sendoka to a real Gmail inbox you control (not example.com). In Gmail, click the message → Show original. Top of the page:

text
SPF:     PASS  with IP 12.34.56.78
DKIM:    'PASS' with domain mail.yourdomain.com
DMARC:   'PASS'

All three PASS = green light to scale. If DKIM is NEUTRAL or missing, the CNAMEs aren't resolving — re-run dig from troubleshooting.

Cloudflare#

Most-popular path; propagation is sub-second.

  1. Cloudflare dashboard → your domain → DNS → Records.
  2. Click Add record. Type: CNAME.
  3. Name: paste the host part shown in Sendoka (e.g. <token1>._domainkey.mail) — Cloudflare auto-appends your root domain.
  4. Target: the SES target (<token1>.dkim.amazonses.com).
  5. Proxy status: set to DNS only (gray cloud). Proxied (orange cloud) breaks DKIM lookup.
  6. Repeat for the other two tokens. Save.
Watch out: Cloudflare proxy must be off for the DKIM CNAMEs. The proxy strips them.

AWS Route 53#

  1. AWS Console → Route 53 → Hosted zones → your domain.
  2. Create record. Routing policy: simple.
  3. Record name: the host part (<token1>._domainkey.mail); the root is implicit.
  4. Record type: CNAME. Value: the SES target.
  5. TTL: leave default (300s). Save.
  6. Repeat for the other two tokens.

Tip: if you're in CloudFormation / Terraform / CDK, templating the three records is one loop. We publish a CDK construct on the roadmap.

Vercel DNS#

  1. Vercel dashboard → your domain → DNS Records.
  2. Add. Type: CNAME.
  3. Name: the full host <token1>._domainkey.mail.yourdomain.com (Vercel asks for the full record).
  4. Value: the SES target.
  5. Save. Repeat for the other two tokens.

Namecheap#

  1. Namecheap dashboard → your domain → Advanced DNS.
  2. Add new record. Type: CNAME Record.
  3. Host: paste the host part (<token1>._domainkey.mail); Namecheap appends the root.
  4. Value: the SES target.
  5. TTL: Automatic. Save with the green checkmark.
  6. Repeat for the other two tokens.

Namecheap propagation can take up to an hour even though the dashboard shows the record immediately.

GoDaddy#

  1. My Products → your domain → DNS.
  2. Add → CNAME.
  3. Name: the host part. Value: the SES target.
  4. TTL: 1 hour (default is fine). Save.
  5. Repeat for the other two tokens.

GoDaddy is slowest of the major providers — 1–4h is typical, sometimes 24h.

Google Domains / Squarespace Domains#

Google Domains migrated to Squarespace in 2023 — UI varies depending on when your domain was migrated.

  1. Domain settings → DNSCustom records.
  2. Add. Type: CNAME.
  3. Host: the host part. Data: the SES target.
  4. Repeat for all three tokens.

Trigger verification#

Once your DNS records are live, ask Sendoka to re-check. The dashboardRe-check button calls the same endpoint:

POSThttps://api.sendoka.com/api/v1/domains/:id/verify

Internally we call SES GetEmailIdentityCommand and compare the DKIM verification status. If VerifiedForSendingStatus = true, your domain row flips to verified and verified_at is stamped.

How long does it take?

  • Cloudflare, Route 53:< 30 seconds, often instant.
  • Vercel, Namecheap: 1–10 minutes typically.
  • GoDaddy, Google: 10 minutes to a few hours.
  • Worst case: 24h. If you're past 24h, see troubleshooting.

Domain warmup#

New sender domains have no reputation. ISPs (Gmail, Outlook) treat a sudden spike from a brand-new domain as suspicious — even when DKIM, SPF, and DMARC all pass.

Sendoka enforces a daily ramp: 50 messages day 1, doubling each day, capped at the plan limit. Past the cap returns 429 WARMUP_LIMIT_EXCEEDED.

You can opt in / configure the window per-domain via warmup_started_at + warmup_days on the row. Most orgs leave it on the default 14-day ramp.

Practical advice

  • Send to your most engaged users first. High open rates early build reputation fast.
  • Avoid bulk newsletters during the first week.
  • Watch the dashboard's Deliverability card — it shows bounce rate by domain, with warn/critical thresholds.

First test send#

Verified? Run a test from your own domain to a real inbox you control. Don't send to example.com — those bounce.

curl -X POST https://api.sendoka.com/api/v1/emails \
  -H "Authorization: Bearer $SENDOKA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "hello@mail.yourdomain.com",
    "to": ["you@your-personal-inbox.com"],
    "subject": "Verification test",
    "html": "<p>If you can read this, DKIM signed correctly.</p>"
  }'

Open the email in Gmail and click "Show original". You want:

  • DKIM: PASS with your domain
  • SPF: PASS (if you added the SPF record)
  • DMARC: PASS (if you added DMARC)

All three passing = green light to scale.

Subdomain strategy#

Recommendation: send from a subdomain, not the root.

Use mail.yourdomain.com or send.yourdomain.com instead of yourdomain.com. Why:

  • Reputation isolation. If a marketing campaign tanks deliverability, your transactional sends from a different subdomain stay clean.
  • Easier DMARC. Strict p=reject on transactional mail without affecting your team's regular email.
  • No conflicts. Existing SPF / DMARC at the root don't interfere.

Common conventions: mail. (transactional), news. (marketing), notifications. (product alerts). Pick one and stick with it.

Multiple domains#

Add as many as you need. Sendoka tracks reputation per-domain, so a transactional domain with p=reject DMARC and a marketing domain with looser policy can coexist.

The send picks DKIM signing key based on the from address — whichever domain matches gets used. Sending from a domain that isn't verified returns 403 DOMAIN_NOT_ALLOWED_FOR_KEY.

You can scope an API key to specific domains via allowed_domain_ids on the key — handy when you want a transactional service to be physically unable to send from a marketing domain even by mistake.

Per-tenant domains (platform mode)#

On the platform tier, each tenant can own its own sender domain — your customer Acme verifies mail.acme.com, your customer Globex verifies mail.globex.com, and the suppression / reputation books are kept separate.

Pass tenant_id when adding the domain (platform-root key) or use X-Sendoka-Tenant-Ref to auto-resolve the tenant slot:

bash
curl -X POST https://api.sendoka.com/api/v1/domains \
  -H "Authorization: Bearer $SENDOKA_PLATFORM_KEY" \
  -H "X-Sendoka-Tenant-Ref: cust_12345" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "mail.acme.com" }'

The domain row gets stamped with tenant_id. Tenant-bound API keys can only send from their own tenant's domains — even if the platform owns other verified domains under the same org.

Read: first-tenant walkthrough →

Remove or rotate#

Remove

DELETEhttps://api.sendoka.com/api/v1/domains/:id

Calls SES DeleteEmailIdentityCommand (best-effort) and removes the domain row. Existing messages stay queryable; the domain just can't be used for new sends.

Rotate

SES doesn't expose a "rotate DKIM keys" call directly — to rotate, delete the domain and re-add it. You'll get three new tokens, publish those CNAMEs, and the old ones can be removed once propagation completes (~1h).

In practice, almost no one rotates DKIM keys. The keys never leave AWS, so there's no leak vector.

Still pending after 24h#

Don't trust your DNS dashboard — check from outside.

bash
# Replace <token>._domainkey.mail.yourdomain.com with one of yours
dig CNAME <token>._domainkey.mail.yourdomain.com +short

# Should return:
# <token>.dkim.amazonses.com.

If dig returns nothing or a wrong target:

  • Cloudflare: proxy is on (orange cloud). Switch to DNS only (gray cloud).
  • Trailing dot: some providers want <token>.dkim.amazonses.com. (with trailing dot), others want it without. Try both if the first doesn't resolve.
  • Wrong record type: these are CNAMEs, not TXT.
  • Subdomain prefix collision: if you have a wildcard CNAME at *.mail.yourdomain.com, more-specific tokens still resolve fine — but check your provider doesn't reject the more-specific record as "duplicate."
  • Hosting provider's DNS overrides yours: if your domain points NS records at Cloudflare but the actual records are at GoDaddy because you forgot to update them, none of this works. Run dig NS yourdomain.com to see the authoritative resolver.

Partial DKIM verification#

You verified one or two of the three tokens but not all three. SES will still report the identity as VerifiedForSendingStatus = true once one passes — but until all three resolve, signing is brittle. Random percentage of sends could fail to sign, depending on which key SES rotates to.

Fix: re-check the missing record(s) with dig, fix at the DNS provider, wait, and trigger verify again.

Sends land in spam#

DKIM passes, SPF passes, but Gmail still routes to spam. Common causes, ranked by likelihood:

  1. You skipped warmup. If you blast 5 000 emails on day 1 from a fresh domain, Gmail tags it as suspicious. Send to engaged users first; ramp slowly.
  2. No DMARC record. Gmail strongly prefers DMARC-aligned senders. Add v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com.
  3. Subject + body trigger spam filters. FREE!!!!, all-caps, lots of exclamation, link shorteners, no plain-text alternative — all signal spam. Test against mail-tester.com for a fresh score.
  4. Recipient never engages. If your contact list hasn't opened anything in 6 months, they're a "sleeper" and lower your sender score. Suppress them.
  5. Reverse DNS / PTR mismatch. Less common with SES but worth checking — SES handles PTR for you on dedicated IPs.

The dashboard's Deliverability card surfaces bounce rate by domain. If it's above 5% you have a list quality problem, not a configuration problem.

Common error codes#

ParameterTypeDescription
DOMAIN_NOT_ALLOWED_FOR_KEY403optionalKey has allowed_domain_ids set; from-domain isn't on the list
DOMAIN_NOT_FOUND422optionalFrom-domain isn't in your org. Add it via /v1/domains.
DOMAIN_PENDING_VERIFICATION422optionalDomain row exists but still pending DKIM. Trigger /verify after DNS propagates.
DOMAIN_TENANT_MISMATCH403optionalTenant-bound key trying to send from a domain owned by a different tenant
WARMUP_LIMIT_EXCEEDED429optionalToday's warmup cap reached. Resets at midnight UTC.
DOMAIN_ALREADY_ADDED409optionalPOST /v1/domains where the domain already exists in the org

API reference#

Endpoints

GEThttps://api.sendoka.com/api/v1/domains
POSThttps://api.sendoka.com/api/v1/domains
GEThttps://api.sendoka.com/api/v1/domains/:id
POSThttps://api.sendoka.com/api/v1/domains/:id/verify
DELETEhttps://api.sendoka.com/api/v1/domains/:id

Scopes

  • read:domains — list, retrieve
  • write:domains — add, verify, remove

Domain object

json
{
  "id": "dom_01HN…",
  "domain": "mail.yourdomain.com",
  "status": "verified",                    // pending | verified
  "tenant_id": null,                       // or "tnt_…" in platform mode
  "dkim_tokens": ["a1b2c3", "d4e5f6", "g7h8i9"],
  "verified_at": "2026-04-30T10:14:22Z",
  "warmup_started_at": "2026-04-30T10:14:22Z",
  "warmup_days": 14,
  "region": "us-east-1",
  "fallback_region": null,
  "created_at": "2026-04-30T09:30:00Z"
}

Try it interactively in the API Reference →