Skip to content
couryo

Developer guideSending

Domains and DNS

How to verify a domain on Couryo. The DKIM (couryo1 and couryo2), return path and DMARC records the API generates, why there is no SPF on your root domain, and how DMARC moves up to p=reject.

View as Markdown

To send with your domain in from, it has to be verified. This proves to mailbox providers (Gmail, Microsoft, Yahoo and others) that Couryo may send on your behalf, and it is the single biggest factor in reaching the inbox.

Add a domain#

Creating a domain needs a key with the admin scope (or the dashboard).

cURL
curl https://api.couryo.com/v1/domains \
  -H "Authorization: Bearer $COURYO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "example.com" }'

The response (201) lists the records you need to create, each with its own status:

Response 201
{
  "id": "dom_7h2kq9w4x1abcdef",
  "name": "example.com",
  "status": "pending",
  "dmarc_policy": "missing",
  "cloudflare_connected": false,
  "records": [
    { "purpose": "dkim", "type": "CNAME", "name": "couryo1._domainkey.example.com", "value": "couryo1.7h2kq9w4x1abcdef.dkim.couryo.com", "status": "pending" },
    { "purpose": "dkim", "type": "CNAME", "name": "couryo2._domainkey.example.com", "value": "couryo2.7h2kq9w4x1abcdef.dkim.couryo.com", "status": "pending" },
    { "purpose": "return_path", "type": "CNAME", "name": "bounces.example.com", "value": "rp.couryo.com", "status": "pending" },
    { "purpose": "dmarc", "type": "TXT", "name": "_dmarc.example.com", "value": "v=DMARC1; p=none; rua=mailto:dmarc@couryo.com; adkim=r; aspf=r", "status": "pending" }
  ],
  "created_at": "2026-10-07T18:30:00Z"
}

Both DKIM targets always follow <selector>.<domain ID without the dom_ prefix>.dkim.couryo.com. The ID above is an example: copy the values shown in the dashboard or in the API response for your domain.

Domains per plan: Free 1, Pro 10, Scale 50 and Enterprise unlimited, across all projects of the account. Above that, the API answers 403 with the domain_limit_reached code; deleting a domain frees the slot. A domain can only be registered once on Couryo (409 domain_exists).

What each record does#

Record Type Name What it is for
DKIM CNAME couryo1._domainkey and couryo2._domainkey Every email is signed with a 2048-bit RSA key of your own domain. Both selectors point to Couryo's zone, where the public keys live: we sign with one while the other one's key is replaced, so keys rotate without you touching DNS.
Return path CNAME bounces The technical envelope address, on a subdomain of yours. It carries SPF (aligned with your domain) and routes bounces to Couryo, which turns them into events.
DMARC TXT _dmarc Tells providers what to do with email pretending to be from your domain, and where to send reports. rua=mailto:dmarc@couryo.com brings those reports to Couryo. It starts at p=none.
Inbound MX Optional, to receive email in Couryo (coming soon).

Why there is no SPF on your root domain#

SPF is checked on the envelope domain (the return path), not on the From domain. Since the envelope uses bounces.example.com, which points to Couryo by CNAME, SPF passes and is aligned with your domain. Leave the SPF of example.com alone: the one you already have (Google Workspace, Microsoft 365...) stays as it is, and Couryo uses none of the 10 DNS lookups SPF allows.

If the response has a return_path record of type MX plus an spf record (TXT v=spf1 include:spf.couryo.com ~all), that is the alternative return path mode. Create both on the bounces. subdomain, never on the root domain.

Verify#

With Cloudflare connected, Couryo creates the records and verifies them on its own. Anywhere else, create the records and request a check (admin scope):

cURL
curl -X POST https://api.couryo.com/v1/domains/dom_7h2kq9w4x1abcdef/verify \
  -H "Authorization: Bearer $COURYO_API_KEY"

The check queries public DNS. Each record comes back with a status:

  • ok: found and correct.
  • pending: not found yet. DNS propagation can take from minutes to a few hours.
  • wrong: found with a different value. The found field shows what is published, so you can compare.

If you already have your own DMARC record, it is respected: the record shows ok and found holds your value. Two DMARC records on the same name invalidate each other and show as wrong.

The domain becomes verified once DKIM, return path and DMARC are ok and Couryo's engine confirms the DKIM signature. Pending domains are checked again automatically every 10 minutes for 72 hours; verified ones, once a day. The first verified domain moves the account from level 0 to level 1 (see Limits and levels).

Connect Cloudflare#

In the dashboard, under Domains, use Connect Cloudflare and paste a token from your account with the Zone > DNS > Edit permission for the domain. Couryo creates the records in your zone and verifies right away. An existing DMARC record is never overwritten.

Possible errors: invalid_cloudflare_token (token refused or missing the permission) and cloudflare_zone_not_found (the token cannot reach the domain's zone).

Tips by DNS provider#

  • Cloudflare (by hand): keep the CNAMEs DNS-only (grey cloud).
  • Other providers: some append the domain automatically. If a record shows up as couryo1._domainkey.example.com.example.com, remove the repeated domain from the name.

DMARC from p=none to p=reject#

DMARC starts at p=none: providers only observe and send reports. Once reports are clean for a few weeks, it is worth moving to p=quarantine (spoofed email goes to spam) and then p=reject (spoofed email is refused). Walking DMARC up with your permission is the deliverability agent's next step (coming soon).

The domain's dmarc_policy field shows the policy published today: none, quarantine, reject or missing.

Other endpoints#

Method and path Scope What it does
GET /v1/domains read lists the project's domains
GET /v1/domains/{id} read one domain, with its records
DELETE /v1/domains/{id} admin removes the domain (204) and frees the plan slot