Appearance
Create a domain
Add a sending domain and get back the DNS records that make it yours to send from.
http
POST /v1/domainsRequires a Full access API key — adding a domain changes what your account can send as, so a sending key embedded in an app cannot create one.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The domain to send from, e.g. example.com. A valid, lowercase domain name; max 253 characters. |
region | string | No | Where the sending identity lives: us-east-1, eu-west-1, sa-east-1 or ap-northeast-1. Defaults to us-east-1. |
custom_return_path | string | No | The subdomain used for the Return-Path (the MAIL FROM domain). A bare label; max 63 characters. Defaults to send, giving send.example.com. |
tracking_subdomain | string | No | A bare label, e.g. links, used as the CNAME host for open and click tracking. When set, a Tracking record is added to the response. |
open_tracking | boolean | No | Rewrite emails to record opens. Only meaningful with a tracking_subdomain. |
click_tracking | boolean | No | Rewrite links to record clicks. Only meaningful with a tracking_subdomain. |
tls | string | No | opportunistic or enforced. Defaults to opportunistic. See TLS. |
capabilities | object | No | What the domain may do. Only sending is available today; see Receiving is not available yet. |
Example
bash
curl -X POST 'https://api.sendgrail.com/v1/domains' \
-H "Authorization: Bearer $SENDGRAIL_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"name": "example.com",
"region": "us-east-1",
"custom_return_path": "send",
"tracking_subdomain": "links",
"open_tracking": true,
"click_tracking": true,
"tls": "opportunistic"
}'js
const res = await fetch('https://api.sendgrail.com/v1/domains', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SENDGRAIL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'example.com',
region: 'us-east-1',
custom_return_path: 'send',
tracking_subdomain: 'links',
open_tracking: true,
click_tracking: true,
tls: 'opportunistic',
}),
});
const domain = await res.json();Response
201 Created. Unlike most creates, this returns the full domain object — records is the whole point of adding a domain, so you get it without a second call.
json
{
"object": "domain",
"id": "5f1c3a9e-0d42-4b7c-9a63-2e8b41d7c015",
"name": "example.com",
"status": "pending",
"created_at": "2026-07-14T19:44:32+00:00",
"region": "us-east-1",
"capabilities": {
"sending": "enabled",
"receiving": "disabled"
},
"open_tracking": true,
"click_tracking": true,
"tracking_subdomain": "links",
"tls": "opportunistic",
"records": [
{
"record": "DKIM",
"name": "sendgrail._domainkey",
"type": "TXT",
"value": "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ...",
"ttl": "Auto",
"status": "pending"
},
{
"record": "SPF",
"name": "send",
"type": "MX",
"value": "feedback-smtp.us-east-1.amazonses.com",
"ttl": "Auto",
"status": "pending",
"priority": 10
},
{
"record": "SPF",
"name": "send",
"type": "TXT",
"value": "v=spf1 include:amazonses.com ~all",
"ttl": "Auto",
"status": "pending"
},
{
"record": "DMARC",
"name": "_dmarc",
"type": "TXT",
"value": "v=DMARC1; p=none;",
"ttl": "Auto",
"status": "pending"
},
{
"record": "Tracking",
"name": "links.example.com",
"type": "CNAME",
"value": "track.sendgrail.com",
"ttl": "Auto",
"status": "pending"
}
]
}For the field-by-field breakdown of the object and of each entry in records, see Retrieve a domain.
The flow: add records, verify, poll
A new domain is pending and cannot send yet. To take it live:
- Add the records. Copy each entry in
recordsinto your DNS provider — thename,typeandvalueas given, with thepriorityon theMXrecord.ttlisAuto; any low value is fine. - Verify. Call Verify a domain to trigger a DNS re-check against the mail provider.
- Poll. Retrieve the domain and watch
statusmove frompendingtoverified. Once verified, you can send from any address at the domain.
The name values are given relative to the zone apex — the form most DNS providers expect, since they append the apex for you. @ means the apex itself.
TLS: opportunistic vs enforced
tls controls how mail to your recipients is encrypted in transit:
opportunistic(default) attempts an encrypted (TLS) connection to the recipient's server and falls back to an unencrypted one if TLS is unavailable. This maximises deliverability — the message always goes out.enforcedrequires TLS. If the recipient's server won't negotiate it, the message is not sent. This is for regulated senders who cannot let a message travel in the clear.
The policy is applied through a per-policy SES configuration set. You can change it later with Update a domain.
Receiving is not available yet
capabilities.sending is always enabled; capabilities.receiving is always disabled. Inbound email is a later release. Asking for it on create is rejected, not ignored:
json
{
"message": "Receiving (inbound email) is not available yet. Domains can send only.",
"code": "receiving_unavailable",
"request_id": "8f2c1e64-1b17-4f8c-9f0e-3b6c1e0f5c31"
}A domain you already have answers 409
Adding a domain your team already has is a 409, not a duplicate:
json
{
"message": "This domain is already on your team.",
"code": "domain_exists",
"request_id": "8f2c1e64-1b17-4f8c-9f0e-3b6c1e0f5c31"
}If another team has already verified this domain, you can take it over — see Claim a domain. A Sending access key answers 403. See Errors.