---
title: DKIM, SPF, DMARC
path: concepts/dkim-spf-dmarc
status: published
---

# DKIM, SPF, DMARC

Email authentication is three protocols working together. DKIM signs your messages so recipients know they came from you. SPF tells recipients which servers are allowed to send for your domain. DMARC tells recipients what to do when the other two disagree.

ScaiSend handles DKIM signing automatically. SPF and DMARC are DNS records you publish. Both are generated for you when you add a sender domain — this page explains what they do so you know what you're publishing.

## DKIM

### What it is

DKIM (DomainKeys Identified Mail, [RFC 6376](https://datatracker.ietf.org/doc/html/rfc6376)) is a cryptographic signature over the message body and selected headers. The signer has a private key; the verifier fetches the public key from DNS and checks the signature.

If the signature verifies, the receiver knows:

- The message body wasn't modified in transit.
- The sender genuinely has control of the private key.

### The DNS record

ScaiSend generates an RSA-2048 keypair per sender domain. The public key is published as a TXT record at:

```
scaisend._domainkey.<your-domain>
```

Value:

```
v=DKIM1; k=rsa; p=<base64-encoded-public-key>
```

`scaisend` is the **selector** — the first label of the record path. Multiple keys can coexist under different selectors (e.g., during rotation, `scaisend` and `scaisend2` can both be valid).

### Signing

When the SMTP service is about to deliver a message, it:

1. Canonicalizes the headers and body (simple or relaxed canonicalization; ScaiSend uses relaxed).
2. Computes an SHA-256 hash of the canonical body.
3. Signs the hash plus a selected set of headers (From, To, Subject, Date, Message-ID, MIME-Version, Content-Type) with the private key.
4. Adds the signature as a `DKIM-Signature:` header.

A typical header looks like:

```
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mail.example.com;
 s=scaisend; t=1713888000; bh=base64body hash;
 h=From:To:Subject:Date:Message-ID;
 b=base64signature
```

### Verifying on the receiving side

When a recipient MX gets the message, it:

1. Parses the `DKIM-Signature` header.
2. Fetches `s._domainkey.d` (the selector at the domain).
3. Decodes the public key.
4. Recomputes the canonicalization and hash.
5. Verifies the signature.

If any of these fail, the DKIM check fails. The receiver won't bounce just for that (typically; depends on DMARC policy) but your deliverability takes a hit.

### Rotation

Rotate keys annually or whenever compromise is suspected. ScaiSend generates a new keypair under a new selector, publishes the new public key, and switches signing over once DNS has propagated:

```bash
curl -X POST https://scaisend.scailabs.ai/api/admin/domains/dom_01HXYZ/rotate-dkim \
  -H "Authorization: Bearer $SCAISEND_JWT"
```

The old selector stays live until you explicitly remove it. Leaves a window where in-flight mail signed with the old key can still be verified.

## SPF

### What it is

SPF (Sender Policy Framework, [RFC 7208](https://datatracker.ietf.org/doc/html/rfc7208)) is a TXT record at the sending domain listing which mail servers are authorized to send for it. It's a simple IP/hostname allowlist.

### The DNS record

ScaiSend uses `include:` to delegate:

```
example.com TXT "v=spf1 include:scaisend.scailabs.ai ~all"
```

Or, for a subdomain sender:

```
mail.example.com TXT "v=spf1 include:scaisend.scailabs.ai ~all"
```

Meaning:

- `v=spf1` — this is an SPF record (version 1).
- `include:scaisend.scailabs.ai` — trust the SPF rules published at `scaisend.scailabs.ai` for the ScaiSend-operated outbound IPs.
- `~all` — soft-fail anything else. Most receivers treat soft-fail as a quarantine signal.

### If you already have an SPF record

You can't have two SPF records. Merge:

**Existing:**
```
example.com TXT "v=spf1 include:_spf.google.com ~all"
```

**After adding ScaiSend:**
```
example.com TXT "v=spf1 include:_spf.google.com include:scaisend.scailabs.ai ~all"
```

### SPF alignment

DMARC requires the SPF-authenticated domain to align with the `From:` header domain. If your `From:` is `hello@mail.example.com` and SPF authorizes `mail.example.com`, they align. If `From:` is `hello@example.com` but SPF is on `mail.example.com`, relaxed DMARC still counts that as aligned (shared organizational domain).

## DMARC

### What it is

DMARC (Domain-based Message Authentication, Reporting, and Conformance, [RFC 7489](https://datatracker.ietf.org/doc/html/rfc7489)) tells receivers what to do when both SPF and DKIM fail to align with the `From:` domain. It's the policy that gives SPF and DKIM teeth.

### The DNS record

ScaiSend generates a DMARC TXT record when you add a domain:

```
_dmarc.mail.example.com TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.com"
```

Meaning:

- `v=DMARC1` — this is a DMARC record.
- `p=quarantine` — if a message fails both SPF and DKIM, put it in spam. (Alternatives: `none` for no action, `reject` for bounce.)
- `rua=mailto:...` — send aggregate reports here.

### Policies: which to use

| Policy | When |
|--------|------|
| `none` | Starting out. Monitoring-only. Reports flow in; nothing is blocked. |
| `quarantine` | Confirmed clean setup. Failures go to spam. Reasonable default. |
| `reject` | Established reputation. Failures are outright rejected. |

Start with `none`. Watch the `rua` reports for a couple of weeks. Look for:

- Mail from your domain that you didn't send (spoofing).
- Mail that you did send but fails DKIM or SPF (misconfigured service).

Once the only things failing are actual spoofs, graduate to `quarantine`. Later, if you're confident, to `reject`.

### Subdomain policy

`sp=` specifies a policy for subdomains. If you want strict enforcement on `mail.example.com` but no policy on `login.example.com`, publish the DMARC record at `_dmarc.example.com` with `sp=none`:

```
_dmarc.example.com TXT "v=DMARC1; p=quarantine; sp=none; rua=mailto:..."
```

### DMARC alignment

DMARC considers SPF-aligned OR DKIM-aligned as a pass. Either authentication getting the `From:` domain right is sufficient. Since ScaiSend always DKIM-signs, alignment is straightforward as long as your DKIM record is published for the same domain that appears in `From:`.

## How they fit together

A message arrives at a recipient MX:

1. **SPF check.** The connecting IP is compared against the SPF record at the envelope-sender domain. Result: pass, neutral, softfail, fail.
2. **DKIM check.** The `DKIM-Signature` header is validated against the public key at `s._domainkey.d`. Result: pass or fail.
3. **DMARC alignment.** The `From:` header domain is compared against the domains that SPF and DKIM authenticated. If either aligns AND that check passed, DMARC passes. Otherwise DMARC fails.
4. **DMARC policy.** If DMARC fails, the receiver consults the DMARC policy (`p=`). `quarantine` → spam folder; `reject` → bounce.

For a well-configured ScaiSend deployment sending from `hello@mail.example.com`:

- SPF: the connecting IP is one of ScaiSend's outbound IPs → listed via `include:scaisend.scailabs.ai` → pass.
- DKIM: signed with the key at `scaisend._domainkey.mail.example.com` → verifies → pass.
- Alignment: both SPF and DKIM identify `mail.example.com`, same as `From:`. → aligned.
- DMARC pass.

## Verifying

Use a mail-test service or send a test message to a Gmail account and check **Show original**. You should see:

```
SPF: PASS with IP ...
DKIM: PASS with domain mail.example.com
DMARC: PASS
```

If any fails, the `errors[]` on `POST /api/admin/domains/{id}/verify` tells you which DNS record is missing or wrong.

## Common failure modes

**"DKIM: PASS with domain scaisend.scailabs.ai, but DMARC fails."**

Your DKIM is publishing under the wrong domain — most likely because you didn't publish the key at `scaisend._domainkey.<your-domain>` and the verifier is falling back to something generic. Republish.

**"SPF: PERMERROR / too many DNS lookups."**

SPF has a 10-lookup limit. If you have a deep chain of `include:` directives, you hit the cap. Flatten the SPF record — replace includes with explicit IPs if necessary.

**"DMARC: FAIL (policy: none)."**

The message failed alignment but your policy is `none` so nothing happens. Check the `rua` report to diagnose why alignment failed. Fix SPF/DKIM, then graduate policy.

## Related

- [Sender Domains](sender-domains) — adding and verifying domains.
- [Admin Reference](../reference/admin) — domain endpoints.
- [Bounce Handling](bounce-handling) — what happens when mail fails.
