Skip to content
On this site

Domains

A domain is the identity your mail is signed with. Add it, publish its DNS records, verify it, and it can send.

The records

POST /domains answers with a records array. Publish every entry, exactly as given, at your DNS provider. The names are relative to the domain you added.

The DNS records a domain needs
RecordTypeNameValue
DKIMTXTrasket._domainkeyA bare p=… value. The key is 2048-bit, so the value is published as two quoted strings — most providers do that for you when you paste it.
MAIL FROMMXsendfeedback-smtp.<region>.amazonses.com, priority 10. This is what makes bounces come back to us instead of to your own mail server.
MAIL FROMTXTsendv=spf1 include:amazonses.com ~all
TrackingCNAMElinkslinks1.rasket-dns.com. Only needed if you turn on open or click tracking, and never proxied.

The first three are what sending needs. The Tracking row appears only once you ask for open or click tracking, and a domain verifies for sending without it — the tracking guide covers it. No CAA record is required for any of them.

Send from a subdomain — send.acme.example rather than acme.example. A subdomain keeps your transactional reputation separate from whatever else your root domain does, and it means these records never collide with the ones your mail provider already publishes.

Publishing them for you

POST /domains/{domain_id}/autoconfigure writes the records through your DNS provider. Cloudflare is the only provider we can configure today. It takes an API token created from the Zone → DNS → Edit template and scoped to this domain's zone; a token covering more than one zone is refused, and a global API key is refused outright.

  • Only the records for capabilities you have enabled are published. Turn tracking on first if you want its record written too.
  • We never delete a record and never overwrite one we did not create. Anything already sitting at one of our names comes back in conflicts for you to resolve.
  • The token is deleted as soon as the records are written unless you pass keep_token. DELETE /domains/{domain_id}/autoconfigure/credential removes a kept one — that stops us using it, not anyone else, so revoke it at Cloudflare too.

Regions

region selects where your mail is signed and relayed from. It is a deliverability and latency choice, not a data residency one: it does not decide where your data is stored. A domain's region is fixed once it is created.

Verification

  • POST /domains/{domain_id}/verify checks now. It is limited to one call per minute per domain.
  • We also re-check every domain on a schedule, so a record you publish will verify on its own without you polling.
  • A record that verified and later stops resolving is reported as drifted rather than silently ignored.

Claiming a domain someone else verified

A domain can be verified on one team at a time, so adding one another team already holds answers 409 resource_locked. POST /domains/claim is how you take it over, and the only way: prove you control the name in DNS.

The claim comes back with a TXT record to publish at the root of the domain. Its value always starts rasket-domain-verification=:

TXT  @  rasket-domain-verification=8Kx2mQ7vN4pR9wL1sT6yU3bC5dF0gH8jK2nM4qP7rS9
  • When the record resolves, the domain moves to your team and the previous owner's copy is switched off. There is no other route — no support ticket and no override.
  • The transfer waits if the current owner verified the domain in the last 72 hours, sent from it in the last 24 hours, or has mail scheduled from it. blocked_reason says which, and we re-check every hour.
  • A claim expires after 7 days. The domain arrives with fresh DKIM records of its own — the previous owner's values are never reusable — so publish those and verify as normal.

TLS

tls is opportunistic by default: we use TLS to the receiving server when it offers it. Setting it to enforced changes what happens when it does not — messages to mail servers that do not support TLS will not be sent and will appear as email.failed. That is the trade: no plaintext delivery, at the cost of some mail not arriving.

DMARC

DMARC is not required to verify a domain and we do not publish one for you. It is worth adding, starting at p=none and tightening once the reports look right:

TXT  _dmarc  v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com

If your domain already publishes _dmarc with p=reject and the MAIL FROM records above are not yet verified, mail you send through us may be rejected outright until alignment is in place. Publish the records first, verify, then send.

Endpoints

POST /domains

Register a sending domain and get the DNS records to publish.

Body

Body
FieldTypeDescription
name*stringThe domain to send from. A subdomain such as send.acme.example is recommended.
regionstringWhere SES signs and relays the mail. Defaults to eu-west-1.
6 more fields (open_tracking, click_tracking, tracking_subdomain, custom_return_path, tls, capabilities)
Body, less common
FieldTypeDescription
open_trackingbooleanRewrite the body to add a tracking pixel. Off by default.
click_trackingbooleanRewrite links so clicks are recorded. Off by default.
tracking_subdomainstringThe label tracking links are served from. Defaults to track.
custom_return_pathstringThe label the MAIL FROM records are published at. Defaults to send.
tlsstringopportunistic or enforced. Enforced refuses to deliver without TLS.
capabilitiesobjectReserved for inbound mail. Sending is the only capability today.
curl -X POST "https://api.rasket.com/domains" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "send.acme.example",
  "region": "eu-west-1"
}'

Response 201

{
  "id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34",
  "name": "send.acme.example",
  "created_at": "2026-09-09T09:02:44.301Z",
  "status": "not_started",
  "region": "eu-west-1",
  "open_tracking": false,
  "click_tracking": false,
  "tracking_subdomain": "track",
  "capabilities": {
    "sending": "pending",
    "receiving": "unsupported"
  },
  "records": [
    {
      "record": "DKIM",
      "name": "rasket._domainkey",
      "type": "TXT",
      "ttl": "Auto",
      "status": "not_started",
      "value": "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA…"
    },
    {
      "record": "SPF",
      "name": "send",
      "type": "MX",
      "ttl": "Auto",
      "status": "not_started",
      "value": "feedback-smtp.eu-west-1.amazonses.com",
      "priority": 10
    },
    {
      "record": "SPF",
      "name": "send",
      "type": "TXT",
      "ttl": "Auto",
      "status": "not_started",
      "value": "v=spf1 include:amazonses.com ~all"
    }
  ]
}
  • Publish every record in records, then call verify. Nothing sends until the domain is verified.
  • region selects where SES signs and relays your mail. It does not decide where your data is stored.
  • A name another team has already verified answers 409 resource_locked.

GET /domains

Every domain on the team.

Query parameters

Query parameters
FieldTypeDescription
limitintegerHow many items to return, 1–100. Defaults to 20.
afterstringReturn the page that follows this item ID. Mutually exclusive with before.
beforestringReturn the page that precedes this item ID. Mutually exclusive with after.
curl -X GET "https://api.rasket.com/domains" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34",
      "name": "send.acme.example",
      "status": "verified",
      "created_at": "2026-09-09T09:02:44.301Z",
      "region": "eu-west-1",
      "open_tracking": false,
      "click_tracking": false,
      "capabilities": {
        "sending": "verified",
        "receiving": "unsupported"
      }
    }
  ]
}
  • The list omits records. Retrieve one domain to see the current state of each record.

GET /domains/{domain_id}

One domain, with the live status of every record.

Path parameters

Path parameters
FieldTypeDescription
domain_id*stringThe domain's ID.
curl -X GET "https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "domain",
  "id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34",
  "name": "send.acme.example",
  "status": "verified",
  "created_at": "2026-09-09T09:02:44.301Z",
  "region": "eu-west-1",
  "open_tracking": false,
  "click_tracking": false,
  "tracking_subdomain": "track",
  "capabilities": {
    "sending": "verified",
    "receiving": "unsupported"
  },
  "records": [
    {
      "record": "DKIM",
      "name": "rasket._domainkey",
      "type": "TXT",
      "ttl": "Auto",
      "status": "not_started",
      "value": "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA…"
    },
    {
      "record": "SPF",
      "name": "send",
      "type": "MX",
      "ttl": "Auto",
      "status": "not_started",
      "value": "feedback-smtp.eu-west-1.amazonses.com",
      "priority": 10
    },
    {
      "record": "SPF",
      "name": "send",
      "type": "TXT",
      "ttl": "Auto",
      "status": "not_started",
      "value": "v=spf1 include:amazonses.com ~all"
    }
  ]
}

PATCH /domains/{domain_id}

Change tracking, TLS or the tracking subdomain.

Path parameters

Path parameters
FieldTypeDescription
domain_id*stringThe domain's ID.

Body

Body
FieldTypeDescription
open_trackingbooleanTurn the tracking pixel on or off.
click_trackingbooleanTurn link rewriting on or off.
3 more fields (tls, tracking_subdomain, capabilities)
Body, less common
FieldTypeDescription
tlsstringopportunistic or enforced.
tracking_subdomainstringChanging this publishes a new record and re-verifies tracking.
capabilitiesobjectReserved for inbound mail.
curl -X PATCH "https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "click_tracking": true
}'

Response 200

{
  "object": "domain",
  "id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34"
}
  • The domain's name and region are fixed at creation. Delete and re-add to change either.

POST /domains/{domain_id}/verify

Check the published records now instead of waiting for the next sweep.

Path parameters

Path parameters
FieldTypeDescription
domain_id*stringThe domain's ID.
curl -X POST "https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34/verify" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "domain",
  "id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34"
}
  • One call per minute per domain. We also re-check every domain on a schedule, so a published record verifies on its own within the hour.
  • Verification reads DNS. A record published seconds ago may still be cached as absent by the resolver.

DELETE /domains/{domain_id}

Remove the domain and release its SES identity.

Path parameters

Path parameters
FieldTypeDescription
domain_id*stringThe domain's ID.
curl -X DELETE "https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "domain",
  "id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34",
  "deleted": true
}
  • Emails already sent from the domain are kept; scheduled ones that have not gone out will fail.
  • Any sending_access key restricted to this domain stops being able to send.

POST /domains/{domain_id}/dkim/regenerate

Mint a new signing key, optionally a shorter one, and republish the DKIM record.

Path parameters

Path parameters
FieldTypeDescription
domain_id*stringThe domain's ID.

Body

Body
FieldTypeDescription
key_bits*integer2048 to rotate at the default size, or 1024 for a DNS panel that will not accept the ~392-character TXT value a 2048-bit key produces.
curl -X POST "https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34/dkim/regenerate" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "key_bits": 1024
}'

Response 200

{
  "object": "domain",
  "id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34",
  "name": "send.acme.example",
  "status": "verified",
  "created_at": "2026-09-09T09:02:44.301Z",
  "region": "eu-west-1",
  "open_tracking": false,
  "click_tracking": false,
  "tracking_subdomain": "track",
  "capabilities": {
    "sending": "verified",
    "receiving": "unsupported"
  },
  "records": [
    {
      "record": "DKIM",
      "name": "rasket._domainkey",
      "type": "TXT",
      "ttl": "Auto",
      "status": "not_started",
      "value": "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA…"
    },
    {
      "record": "SPF",
      "name": "send",
      "type": "MX",
      "ttl": "Auto",
      "status": "not_started",
      "value": "feedback-smtp.eu-west-1.amazonses.com",
      "priority": 10
    },
    {
      "record": "SPF",
      "name": "send",
      "type": "TXT",
      "ttl": "Auto",
      "status": "not_started",
      "value": "v=spf1 include:amazonses.com ~all"
    }
  ]
}
  • Publish the DKIM record's new value before your next send. The old key stops signing immediately, and the domain is not verified for sending again until we see the new record.
  • One call per minute per domain, shared with Verify a domain.

POST /domains/claim

Take over a domain another team has already verified, by proving you control it.

Body

Body
FieldTypeDescription
name*stringThe domain to claim. Adding it with POST /domains answered 409 for this name.
2 more fields (region, custom_return_path)
Body, less common
FieldTypeDescription
regionstringWhere your mail will be sent from once the claim completes. It does not have to match the current owner's.
custom_return_pathstringThe Return-Path subdomain, as on POST /domains. Defaults to send.
curl -X POST "https://api.rasket.com/domains/claim" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "example.com",
  "region": "us-east-1"
}'

Response 201

{
  "object": "domain_claim",
  "id": "c1f2b3a4-1176-453e-8fc1-35364d380206",
  "name": "example.com",
  "status": "pending",
  "domain_id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34",
  "region": "us-east-1",
  "record": {
    "record": "Claim",
    "name": "example.com",
    "type": "TXT",
    "ttl": "Auto",
    "status": "pending",
    "value": "rasket-domain-verification=8Kx2mQ7vN4pR9wL1sT6yU3bC5dF0gH8jK2nM4qP7rS9"
  },
  "blocked_reason": null,
  "failure_reason": null,
  "created_at": "2026-09-09T00:00:00.000Z",
  "expires_at": "2026-09-16T00:00:00.000Z"
}
  • Publish the returned TXT record at the root of the domain. We check it on a schedule; when it resolves, the domain moves to your team and the previous owner's copy is switched off.
  • The transfer waits if the current owner verified the domain in the last 72 hours, sent from it in the last 24 hours, or has scheduled mail from it. blocked_reason says which, and we re-check every hour.
  • A claim expires after 7 days. Calling this again while a claim is open returns that claim unchanged with 200 rather than starting a second one.
  • The domain arrives with new DKIM records of its own: the previous owner's values are never reusable. Publish them and verify the domain as normal.

GET /domains/{domain_id}/claim

The latest claim for a domain, and the record that proves ownership.

Path parameters

Path parameters
FieldTypeDescription
domain_id*stringThe placeholder domain the claim created — not the claim's own ID.
curl -X GET "https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34/claim" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "domain_claim",
  "id": "c1f2b3a4-1176-453e-8fc1-35364d380206",
  "name": "example.com",
  "status": "pending",
  "domain_id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34",
  "region": "us-east-1",
  "record": {
    "record": "Claim",
    "name": "example.com",
    "type": "TXT",
    "ttl": "Auto",
    "status": "pending",
    "value": "rasket-domain-verification=8Kx2mQ7vN4pR9wL1sT6yU3bC5dF0gH8jK2nM4qP7rS9"
  },
  "blocked_reason": null,
  "failure_reason": null,
  "created_at": "2026-09-09T00:00:00.000Z",
  "expires_at": "2026-09-16T00:00:00.000Z"
}
  • Poll this to follow a claim: pending while we look for the record, blocked with a reason while the current owner is still active, completed once the domain is yours.

POST /domains/{domain_id}/claim/verify

Ask for the ownership record to be checked sooner.

Path parameters

Path parameters
FieldTypeDescription
domain_id*stringThe placeholder domain the claim created — not the claim's own ID.
curl -X POST "https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34/claim/verify" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "domain_claim",
  "id": "c1f2b3a4-1176-453e-8fc1-35364d380206",
  "name": "example.com",
  "status": "pending",
  "domain_id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34",
  "region": "us-east-1",
  "record": {
    "record": "Claim",
    "name": "example.com",
    "type": "TXT",
    "ttl": "Auto",
    "status": "pending",
    "value": "rasket-domain-verification=8Kx2mQ7vN4pR9wL1sT6yU3bC5dF0gH8jK2nM4qP7rS9"
  },
  "blocked_reason": null,
  "failure_reason": null,
  "created_at": "2026-09-09T00:00:00.000Z",
  "expires_at": "2026-09-16T00:00:00.000Z"
}
  • The claim stays pending while the check runs; read the claim back to see the result.
  • One call per minute per domain, shared with Verify a domain.

POST /domains/{domain_id}/autoconfigure

Publish this domain's records through the customer's DNS provider.

Path parameters

Path parameters
FieldTypeDescription
domain_id*stringThe domain's ID.

Body

Body
FieldTypeDescription
provider*stringcloudflare. The only provider we can configure today.
token*stringA Cloudflare API token created from the Zone → DNS → Edit template, scoped to this domain's zone and no other. Global API keys are refused.
keep_tokenbooleantrue to keep the token so you can re-apply records later. It is deleted as soon as the records are written otherwise.
curl -X POST "https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34/autoconfigure" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0" \
  -H "Content-Type: application/json" \
  -d '{
  "provider": "cloudflare",
  "token": "your-cloudflare-api-token",
  "keep_token": false
}'

Response 200

{
  "object": "domain",
  "id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34",
  "created": ["DKIM:TXT:rasket._domainkey", "SPF:MX:send", "SPF:TXT:send"],
  "updated": [],
  "conflicts": [],
  "credential_kept": false
}
  • Only records for capabilities you have enabled are published. Turning on receiving or tracking first, then running this, is what publishes those records.
  • We never delete a record, and never overwrite one we did not create. Anything already sitting at one of our names comes back in conflicts for you to resolve.
  • The token is encrypted at rest and only ever used for this domain's zone. Revoke it at Cloudflare when you are done — deleting it here stops us using it, not anyone else.
  • One call per minute per domain, shared with Verify a domain.

DELETE /domains/{domain_id}/autoconfigure/credential

Remove the provider credential kept for this domain.

Path parameters

Path parameters
FieldTypeDescription
domain_id*stringThe domain's ID.
curl -X DELETE "https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34/autoconfigure/credential" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "domain",
  "id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34",
  "deleted": true
}
  • Records already published are untouched; only our copy of the token goes away.
  • Answers 404 when no token is stored for this domain.

GET /domains/{domain_id}/records

Each record to publish, beside what DNS answered for it on the last check.

Path parameters

Path parameters
FieldTypeDescription
domain_id*stringThe domain's ID.
curl -X GET "https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34/records" \
  -H "Authorization: Bearer $RASKET_API_KEY" \
  -H "User-Agent: acme-billing/1.0"

Response 200

{
  "object": "domain_records",
  "domain_id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34",
  "sending_verified": false,
  "tracking_verified": false,
  "receiving_verified": false,
  "records": [
    {
      "record": "DKIM",
      "name": "rasket._domainkey",
      "type": "TXT",
      "ttl": "Auto",
      "value": "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA…",
      "status": "failed",
      "observed": [],
      "reason": "not_found",
      "last_checked_at": "2026-09-09T09:30:00.000Z"
    }
  ]
}
  • A record with nothing in observed was not found at its name. One whose observed value differs from value was published wrong.
  • This reads the last check. Ask for a new one with POST /domains/{domain_id}/verify.