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.
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 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:
{
"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
403with thedomain_limit_reachedcode; 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_pathrecord of typeMXplus anspfrecord (TXT v=spf1 include:spf.couryo.com ~all), that is the alternative return path mode. Create both on thebounces.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 -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. Thefoundfield 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 |