Skip to content

Create a domain

Add a sending domain and get back the DNS records that make it yours to send from.

http
POST /v1/domains

Requires 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

ParameterTypeRequiredDescription
namestringYesThe domain to send from, e.g. example.com. A valid, lowercase domain name; max 253 characters.
regionstringNoWhere the sending identity lives: us-east-1, eu-west-1, sa-east-1 or ap-northeast-1. Defaults to us-east-1.
custom_return_pathstringNoThe subdomain used for the Return-Path (the MAIL FROM domain). A bare label; max 63 characters. Defaults to send, giving send.example.com.
tracking_subdomainstringNoA 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_trackingbooleanNoRewrite emails to record opens. Only meaningful with a tracking_subdomain.
click_trackingbooleanNoRewrite links to record clicks. Only meaningful with a tracking_subdomain.
tlsstringNoopportunistic or enforced. Defaults to opportunistic. See TLS.
capabilitiesobjectNoWhat 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:

  1. Add the records. Copy each entry in records into your DNS provider — the name, type and value as given, with the priority on the MX record. ttl is Auto; any low value is fine.
  2. Verify. Call Verify a domain to trigger a DNS re-check against the mail provider.
  3. Poll. Retrieve the domain and watch status move from pending to verified. 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.
  • enforced requires 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.

Transactional email on your own domain.