Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Sender Domains

Every live From: address has to use a verified sender domain. Verification proves you control the domain and lets ScaiSend sign your mail with DKIM. Without it, modern mail providers (Gmail, Outlook, Yahoo) will at best rate-limit you and at worst drop your mail silently.

This page walks through what verification is, how to add a domain, and the records you need to publish.

What a "verified sender domain" means#

A domain is verified when all three checks pass:

Check What's verified Record
DKIM The public key in DNS matches the key ScaiSend holds TXT scaisend._domainkey.<domain>
SPF ScaiSend is authorized to send for the domain TXT <domain>
DMARC A DMARC policy exists at _dmarc.<domain> TXT _dmarc.<domain>

SPF and DMARC are technically optional — a message can deliver with DKIM alone — but missing them drops you into Gmail's "unverified sender" path. Publish all three.

Adding a domain#

Create the record in ScaiSend first; that generates the DKIM keypair you'll publish in DNS.

bash
1
2
3
4
5
6
7
8
curl -X POST https://scaisend.scailabs.ai/api/admin/domains \
  -H "Authorization: Bearer $SCAISEND_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "mail.example.com",
    "dmarc_policy": "quarantine",
    "dmarc_rua_email": "dmarc-reports@example.com"
  }'
python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import os
import httpx

resp = httpx.post(
    "https://scaisend.scailabs.ai/api/admin/domains",
    headers={"Authorization": f"Bearer {os.environ['SCAISEND_JWT']}"},
    json={
        "domain": "mail.example.com",
        "dmarc_policy": "quarantine",
        "dmarc_rua_email": "dmarc-reports@example.com",
    },
)
domain = resp.json()
print(domain["dns_records"])
typescript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const resp = await fetch("https://scaisend.scailabs.ai/api/admin/domains", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SCAISEND_JWT}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    domain: "mail.example.com",
    dmarc_policy: "quarantine",
    dmarc_rua_email: "dmarc-reports@example.com",
  }),
});
const domain = await resp.json();
console.log(domain.dns_records);

Response:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
  "id": "dom_01HXYZ",
  "domain": "mail.example.com",
  "verified": false,
  "dkim_selector": "scaisend",
  "dmarc_policy": "quarantine",
  "dmarc_rua_email": "dmarc-reports@example.com",
  "is_shared": false,
  "is_active": true,
  "dns_records": [
    {
      "type": "TXT",
      "host": "scaisend._domainkey.mail.example.com",
      "value": "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
    },
    {
      "type": "TXT",
      "host": "mail.example.com",
      "value": "v=spf1 include:scaisend.scailabs.ai ~all"
    },
    {
      "type": "TXT",
      "host": "_dmarc.mail.example.com",
      "value": "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.com"
    }
  ]
}

Copy dns_records and publish every row at your DNS provider.

Getting the records any time#

If you lost the response, refetch:

bash
1
2
curl https://scaisend.scailabs.ai/api/admin/domains/dom_01HXYZ/dns-records \
  -H "Authorization: Bearer $SCAISEND_JWT"

Verifying#

After DNS has propagated (cheap providers: minutes; slow providers: hours), run the verification:

bash
1
2
curl -X POST https://scaisend.scailabs.ai/api/admin/domains/dom_01HXYZ/verify \
  -H "Authorization: Bearer $SCAISEND_JWT"

Response:

json
1
2
3
4
5
6
7
8
{
  "domain": "mail.example.com",
  "verified": true,
  "dkim_verified": true,
  "spf_verified": true,
  "txt_verified": true,
  "errors": []
}

If any check fails, errors[] tells you which record is missing or mismatched. Fix the DNS, wait for TTL, rerun. Verification is idempotent.

Once verified, the domain becomes usable as a From: address for any live send from this tenant.

Rotating DKIM keys#

Rotate annually or whenever a key might be compromised. Rotation generates a new keypair, publishes the new public key as scaisend2._domainkey.<domain> (different selector), and switches signing over once DNS propagates.

bash
1
2
curl -X POST https://scaisend.scailabs.ai/api/admin/domains/dom_01HXYZ/rotate-dkim \
  -H "Authorization: Bearer $SCAISEND_JWT"
python
1
2
3
4
5
6
7
import os, httpx

resp = httpx.post(
    f"https://scaisend.scailabs.ai/api/admin/domains/{domain_id}/rotate-dkim",
    headers={"Authorization": f"Bearer {os.environ['SCAISEND_JWT']}"},
)
print(resp.json()["new_selector"])

Response includes the new selector and the new public key TXT record. Publish it, wait for propagation, then re-verify. The old selector keeps working until you explicitly remove it — messages in flight signed with the old key remain verifiable.

Shared sender domains#

Partner operators can mark a domain as is_shared: true. Shared domains can be used as a From: address by any tenant under the partner.

bash
1
2
3
4
curl -X POST https://scaisend.scailabs.ai/api/admin/domains \
  -H "Authorization: Bearer $SCAISEND_JWT" \
  -H "Content-Type: application/json" \
  -d '{"domain": "noreply.platform.example", "is_shared": true}'

Use cases:

  • A platform-wide "noreply" for password resets, login codes, receipts.
  • A branded sender for customers who don't have their own verified domain yet.

Shared domains are owned by the partner; individual tenants can't modify them.

DMARC policies#

dmarc_policy controls what receivers should do with mail that fails both SPF and DKIM alignment. Three values:

Policy Meaning
none Monitor only. Failures are reported but not blocked. Safe default when you're just starting.
quarantine Failures go to spam. Reasonable once you're confident your setup is correct.
reject Failures are bounced hard. Appropriate for established sender reputations.

Start with none, check the DMARC aggregate reports sent to your dmarc_rua_email for a week or two, then graduate to quarantine once you've confirmed there's no legitimate mail being misattributed to you.

See DKIM, SPF, DMARC for the deeper treatment.

What happens if the domain isn't verified#

You'll hit one of these:

Situation Behavior
sg_live_* send, unverified From: domain Request is accepted (202) but the message ends in status failed with a "domain not verified" error in the event timeline.
sg_test_* send, unverified From: domain Accepted, status sandbox. Sandbox mode skips the domain check because no delivery happens.
sg_live_* send, verified domain but DKIM DNS removed Message sends but DKIM signature is invalid. Receivers will probably drop or quarantine. Re-verify.

Deactivating a domain#

Deactivate to prevent further sends without deleting the record:

bash
1
2
3
4
curl -X PATCH https://scaisend.scailabs.ai/api/admin/domains/dom_01HXYZ \
  -H "Authorization: Bearer $SCAISEND_JWT" \
  -H "Content-Type: application/json" \
  -d '{"is_active": false}'

Sends with an inactive domain are rejected at queue time. Reactivate by setting is_active: true. Use this when you're rotating sender infrastructure; delete the record outright only when you're retiring the domain.

What's next#

Updated 2026-05-17 01:33:26 View source (.md) rev 1