Domains: Add, DNS Setup & Verification
Set up sending domains with proper DNS authentication
A domain is your verified sending identity. Before you send live emails, you must add a domain and publish the DNS records Tratto returns for it.
Why Domains Matter
- Sender Authentication: Prove you control the domain
- Deliverability: SPF, DKIM, DMARC prevent spoofing and improve inbox placement
- Trust: Recipients see your branded domain, not a third-party service
Add a Domain
Register your domain with Tratto. The number of domains is capped per plan and
counted across all the workspaces of the account, including domains still
pending and those that failed: past the cap the request fails with
429 QUOTA_EXCEEDED. See Plan Limits.
In the dashboard only the workspace owner adds, verifies and removes domains: a member can read them, and gets 403 FORBIDDEN (Only the workspace owner can do this.) on the rest. See Team.
cURL
curl -X POST https://api.tratto.email/v1/domains \
-H "Authorization: Bearer tratto_live_..." \
-H "Content-Type: application/json" \
-d '{"domain":"hello.yourdomain.com"}'Node.js
const response = await fetch('https://api.tratto.email/v1/domains', {
method: 'POST',
headers: {
'Authorization': 'Bearer tratto_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({ domain: 'hello.yourdomain.com' }),
});
const { data } = await response.json();
console.log('Domain ID:', data.id);Python
import requests
response = requests.post(
'https://api.tratto.email/v1/domains',
headers={
'Authorization': 'Bearer tratto_live_...',
'Content-Type': 'application/json',
},
json={'domain': 'hello.yourdomain.com'},
)
data = response.json()['data']
print(f"Domain ID: {data['id']}")PHP
$tratto = new \Tratto\Client(getenv('TRATTO_API_KEY'));
$domain = $tratto->domains->add('hello.yourdomain.com');
echo "Domain ID: {$domain['id']}\n";Response (201; values are examples, always use the ones from your response):
{
"data": {
"id": "dom_abc123",
"domain": "hello.yourdomain.com",
"status": "pending",
"records": [
{ "type": "CNAME", "host": "token1._domainkey.hello.yourdomain.com", "value": "token1.dkim.amazonses.com", "verified": false, "purpose": "dkim" },
{ "type": "CNAME", "host": "token2._domainkey.hello.yourdomain.com", "value": "token2.dkim.amazonses.com", "verified": false, "purpose": "dkim" },
{ "type": "CNAME", "host": "token3._domainkey.hello.yourdomain.com", "value": "token3.dkim.amazonses.com", "verified": false, "purpose": "dkim" },
{ "type": "TXT", "host": "hello.yourdomain.com", "value": "v=spf1 include:amazonses.com ~all", "verified": false, "purpose": "spf" },
{ "type": "TXT", "host": "_dmarc.hello.yourdomain.com", "value": "v=DMARC1; p=quarantine; rua=mailto:dmarc@hello.yourdomain.com", "verified": false, "purpose": "dmarc" },
{ "type": "TXT", "host": "_tratto.hello.yourdomain.com", "value": "tratto-verify=4f1c9a0e...", "verified": false, "purpose": "ownership" }
],
"createdAt": "2026-06-30T12:00:00.000Z",
"updatedAt": "2026-06-30T12:00:00.000Z",
"verifiedAt": null
}
}Domain status is pending: Tratto is waiting for Amazon SES, which delivers your email, to find the DKIM records, and for the ownership record to be published.
DNS Records Setup
Add the records from records at your DNS provider (GoDaddy, Namecheap, Cloudflare, Route 53, etc.). Every domain gets six:
| Purpose | Type | Host | Value | Needed to send |
|---|---|---|---|---|
| DKIM | CNAME | <token1>._domainkey.hello.yourdomain.com | <token1>.dkim.amazonses.com | Yes |
| DKIM | CNAME | <token2>._domainkey.hello.yourdomain.com | <token2>.dkim.amazonses.com | Yes |
| DKIM | CNAME | <token3>._domainkey.hello.yourdomain.com | <token3>.dkim.amazonses.com | Yes |
| SPF | TXT | hello.yourdomain.com | v=spf1 include:amazonses.com ~all | Recommended; if the host already has an SPF record, add include:amazonses.com to it |
| DMARC | TXT | _dmarc.hello.yourdomain.com | v=DMARC1; p=quarantine; rua=mailto:dmarc@hello.yourdomain.com | Recommended |
| Ownership | TXT | _tratto.hello.yourdomain.com | tratto-verify=<token> | Yes |
1. DKIM Records (required)
Type: CNAME, three records
Host: <token>._domainkey.hello.yourdomain.com
Value: <token>.dkim.amazonses.com
The three tokens are unique to your domain: copy each host and value from your response. Amazon SES generates and manages the signing key, and the CNAME records point to its public keys. All three must resolve before the domain becomes verified, together with the ownership record.
host is the full name. If your DNS provider appends your domain automatically, enter only the part before it (for example <token1>._domainkey.hello in the yourdomain.com zone), or the record ends up as <token1>._domainkey.hello.yourdomain.com.yourdomain.com.
2. SPF Record (recommended)
Type: TXT
Host: hello.yourdomain.com
Value: v=spf1 include:amazonses.com ~all
This authorizes Amazon SES to send for the hostname.
A hostname must have exactly one v=spf1 record (RFC 7208, section 3.2). With two, the SPF check fails with a permanent error (permerror) for every sender on that hostname, not only Amazon SES. If the host already has an SPF record, for example from Cloudflare Email Routing, Google Workspace or Microsoft 365, add include:amazonses.com to it instead of creating a second one:
v=spf1 include:amazonses.com include:_spf.mx.cloudflare.net ~all
3. DMARC Record (recommended)
Type: TXT
Host: _dmarc.hello.yourdomain.com
Value: v=DMARC1; p=quarantine; rua=mailto:dmarc@hello.yourdomain.com
This asks receiving servers to quarantine mail that fails authentication. The policy covers all mail sent as this domain, not only mail sent through Tratto, so make sure your other senders authenticate before you publish it. Keep a single DMARC record per host. Tratto doesn't check this record, so you can adapt the policy and the rua address. If you are not sure that every other service sending as this domain authenticates, start with p=none, which only asks for reports, read them for a few weeks, then move to p=quarantine.
The suggested rua is a mailbox on the domain itself, because aggregate reports list the IPs and volumes of every source sending as your domain, Tratto or not, and that data belongs to you. A rua on a different domain only works if that domain publishes an authorization record for yours (RFC 7489, section 7.1); without it, receivers drop the reports. Tratto doesn't check that the mailbox exists, and doesn't receive or read your reports: create it, or point rua at an address on the same domain that you already read.
4. Ownership Record (required)
Type: TXT
Host: _tratto.hello.yourdomain.com
Value: tratto-verify=<token>
This record proves that your workspace controls the domain's DNS. The DKIM records alone do not: a domain removed from one workspace and added to another would otherwise be verified by the records the previous owner left in place. The token belongs to your workspace and to this exact domain, so a record published for another workspace never matches.
- Copy the value from the record with
purpose: "ownership"inrecords, returned byPOST /v1/domainsandGET /v1/domains/{id}. - The token stays the same if you remove the domain and add it again in the same workspace.
- A subdomain and its parent domain are two domains:
hello.yourdomain.comandyourdomain.comin the same workspace need two TXT records,_tratto.hello.yourdomain.comand_tratto.yourdomain.com, with different tokens. - A domain that is already
verifieddoes not need it: Tratto checks the record only when a domain moves toverified.
Verification
Tratto checks two things: it asks Amazon SES whether it has found the three DKIM CNAME records, then looks up the ownership TXT record itself. The domain becomes verified only when both are found. SPF and DMARC are never checked: their verified flag is always false, even on a verified domain. The three DKIM records share one flag, which turns true when Amazon SES has found them. The ownership record has its own flag, which turns true when Tratto finds the TXT record. Tratto looks up the TXT record only once the DKIM records are found, so while DKIM is still pending the ownership flag stays false, even if the TXT record is already published.
Checks happen in two ways:
- Automatically: a background job re-checks every
pendingdomain every 10 minutes - On demand:
POST /v1/domains/{id}/verifychecks right away
Amazon SES notes that DNS changes can take up to 72 hours to propagate, and it keeps looking for the DKIM records for up to 72 hours.
If the ownership record was looked up before you published it, a DNS resolver can remember that it was missing for as long as your zone's negative-caching TTL (RFC 2308), so the domain can stay pending for that long after you publish it. If the lookup itself fails (a DNS timeout or server error), the status stays as it was and the next check tries again.
Verify Now
cURL
curl -X POST https://api.tratto.email/v1/domains/dom_abc123/verify \
-H "Authorization: Bearer tratto_live_..."Node.js
const response = await fetch('https://api.tratto.email/v1/domains/dom_abc123/verify', {
method: 'POST',
headers: { 'Authorization': 'Bearer tratto_live_...' },
});
const { data } = await response.json();
console.log('Status:', data.status);Python
import requests
response = requests.post(
'https://api.tratto.email/v1/domains/dom_abc123/verify',
headers={'Authorization': 'Bearer tratto_live_...'},
)
data = response.json()['data']
print(f"Status: {data['status']}")PHP
$tratto = new \Tratto\Client(getenv('TRATTO_API_KEY'));
$domain = $tratto->domains->verify('dom_abc123');
echo "Status: {$domain['status']}\n";The response is the full domain, records included, with 200 whatever the outcome: read status, not the HTTP code.
| Status | Meaning |
|---|---|
pending | Amazon SES hasn't found all three DKIM records yet, the ownership record is not published yet, or a temporary SES or DNS issue prevented the check. Wait, or check again later. |
verified | The DKIM records and the ownership record were found. Live emails can be sent from this domain. |
failed | Amazon SES stopped looking, typically because it couldn't find the DKIM records. The automatic check skips failed domains: see DNS Troubleshooting. |
What verified Unlocks
- Live sends: an email sent with a live key (
tratto_live_...) is accepted only if itsfromdomain is verified in your workspace. Otherwise the API returns403withFORBIDDEN. - Exact match: the check compares the full domain after
@. Verifyingyourdomain.comdoes not coverhello.yourdomain.com: add and verify each domain you send from. - Test sends: test keys (
tratto_test_...) skip this check. See Test mode. - General default sender: when a domain first becomes
verifiedand your workspace has no general default sender (defaultFromEmail), Tratto sets it tonoreply@on that domain. The per-type senders (marketing, automation, transactional) are not set automatically: see Senders.
After Verification
The automatic check looks only at pending domains. Once a domain is
verified, nothing re-checks it on a schedule:
- If you later remove or change a DKIM CNAME or the ownership TXT record, the
domain stays
verifiedin Tratto. Keep all the records in place for as long as you send from the domain. POST /v1/domains/{id}/verifyon a verified domain asks Amazon SES for the current DKIM status again. If SES no longer reports the DKIM records as found, the domain goes back topendingorfailed, and live emails from it are refused until it is verified again. The ownership record is not looked up again while the domain staysverified.- SPF and DMARC are not checked, before or after verification.
Check Domain Status
Get a domain with its DNS records.
cURL
curl https://api.tratto.email/v1/domains/dom_abc123 \
-H "Authorization: Bearer tratto_live_..."Response:
{
"data": {
"id": "dom_abc123",
"domain": "hello.yourdomain.com",
"status": "verified",
"records": [ ... ],
"createdAt": "2026-06-30T12:00:00.000Z",
"updatedAt": "2026-06-30T12:10:00.000Z",
"verifiedAt": "2026-06-30T12:10:00.000Z"
}
}List Domains
Get all domains for your tenant. Each item has id, domain, status and timestamps, but no records: fetch a single domain to see them. Paginate with limit (1 to 100, default 50) and after.
cURL
curl https://api.tratto.email/v1/domains \
-H "Authorization: Bearer tratto_live_..."Remove a Domain
Delete a domain. Its identity in Amazon SES is deleted too.
cURL
curl -X DELETE https://api.tratto.email/v1/domains/dom_abc123 \
-H "Authorization: Bearer tratto_live_..."Removing a domain cannot be undone. Live emails from that domain are rejected until you add it again and verify it with the records from the new response.
DNS Troubleshooting
Still pending after adding records
Cause: DNS hasn't propagated yet, or a DKIM or ownership record doesn't match.
Fix:
- Compare each DKIM and ownership host and value with
recordsfromGET /v1/domains/{id} - Check that your provider didn't append your domain to the host a second time
- Use MXToolbox or
dig CNAME <token1>._domainkey.hello.yourdomain.comanddig TXT _tratto.hello.yourdomain.comto check that the records are live - Wait for the next automatic check (every 10 minutes), or call
POST /v1/domains/{id}/verify
SPF and DMARC don't affect the status: a missing SPF or DMARC record never keeps a domain pending.
Domain is failed
Cause: Amazon SES stopped looking, typically because it couldn't find the DKIM records.
Fix: correct the DKIM records, then call POST /v1/domains/{id}/verify, which reads the current status from Amazon SES. If the domain stays failed, remove it, add it again and publish the records from the new response.
Which Records Tratto Checks
| Record | Purpose | Checked by Tratto |
|---|---|---|
| DKIM | Signs every email with a key Amazon SES manages for your domain | Yes, required to send |
| SPF | Authorizes Amazon SES to send for the hostname | No, recommended |
| DMARC | Instructs mail servers how to handle failed SPF/DKIM | No, recommended |
| Ownership | Proves your workspace controls the domain's DNS | Yes, required to send |
Best practice: Publish all six records for maximum deliverability.
Subdomain Sending
You can use subdomains for sending (e.g., hello.yourdomain.com) while your main domain stays clean. Tratto matches the from domain exactly, so add and verify the subdomain itself, with its own ownership record.
Advantages:
- Isolate email reputation from main domain
- Test new domains without affecting production
- Separate sending and receiving infrastructure
Next Steps
- Send your first email? Go to Send Email
- Test domain setup? Use Quickstart
- Need help with DNS? See Deliverability for best practices
Edit this page on GitHub
Last updated on
Email Footer & Branding
When Tratto appends a footer to your emails (the "Sent using Tratto" badge, mandatory on some plans and opt-in on the others) and how to set your brand colours and logo through the API.
Senders
Set a default sender for marketing, automation and transactional email, and see which address each send uses.