---
title: "DKIM for Multiple Domains: SES, Google, M365, Postfix"
description: "Set up DKIM for multiple domains: a key per domain, selectors that never clash, DMARC alignment, and exact records for SES, Google, M365, and OpenDKIM."
source: "https://www.mailmark.dev/blog/dkim-multiple-domains"
site: "Mailmark"
---

# DKIM for Multiple Domains: SES, Google, M365, Postfix

To set up DKIM for multiple domains, give every domain you send From its own key: generate DKIM separately for each domain in each service that sends as it, and publish that service's records under `selector._domainkey.yourdomain` in that domain's DNS. The signature's `d=` must match the From domain (or its parent) for DMARC to count it, so one domain's key can't vouch for another product's mail. Selectors only have to be unique within a domain, so reusing names like `google` or `selector1` across domains is fine.

> **A separate DKIM key for every domain you add** Each domain you add to Mailmark gets its own Amazon SES identity with a 2048-bit Easy DKIM key and three CNAMEs to paste. Mailmark checks each of the three records on its own, so you can see exactly which one is missing. [Add your domains](https://www.mailmark.dev/?utm_source=guide&utm_medium=seo&utm_campaign=dkim-multiple-domains&utm_content=top)

## How DKIM finds the right key for each domain

Every DKIM signature carries two tags that matter here: `d=` (the signing domain) and `s=` (the selector). A receiver fetches the public key from a fixed location built from those two values. [RFC 6376 section 3.6.2.1](https://datatracker.ietf.org/doc/html/rfc6376#section-3.6.2.1) gives the example: a signature with `d=example.com` and `s=foo.bar` is verified with the key at `foo.bar._domainkey.example.com`.

Three things follow from that, and they answer most multi-domain questions:

- **Keys are namespaced by domain.** `google._domainkey.product-a.com` and `google._domainkey.product-b.com` are different DNS names. The same selector on two domains never clashes.
- **One domain can hold many keys.** Each service picks its own selector, so Google Workspace, Amazon SES, and a newsletter tool can all sign for the same domain at once.
- **The key proves the `d=` domain, nothing more.** A valid signature from `d=product-a.com` says nothing about mail whose From address is on `product-b.com`.

## Why each From domain needs its own signature

DKIM on its own will happily pass a message signed by any domain. DMARC is what ties the signature to the address people see. Under [RFC 7489 section 3.1.1](https://datatracker.ietf.org/doc/html/rfc7489#section-3.1.1), relaxed alignment (the default) requires the `d=` domain and the From domain to share the same organizational domain. [Microsoft puts it plainly](https://learn.microsoft.com/en-us/defender-office-365/email-authentication-dkim-configure): a message needs to be DKIM signed by the domain in the From address.

| From address | Signature `d=` | DKIM | DMARC alignment |
| --- | --- | --- | --- |
| `hi@product-a.com` | `product-a.com` | Pass | Aligned |
| `hi@news.product-a.com` | `product-a.com` | Pass | Aligned (same organizational domain) |
| `hi@product-b.com` | `product-a.com` | Pass | Not aligned |
| `hi@product-b.com` | your provider's domain | Pass | Not aligned |

The last two rows are the multi-domain trap. Everything looks fine in a DKIM checker, but DMARC fails unless SPF happens to align instead. Gmail requires the From domain to align with either the SPF or the DKIM domain for direct mail ([Google sender guidelines](https://support.google.com/mail/answer/81126)), so signing every product with one "main" domain doesn't work.

Subdomains are the exception that saves work. Because of relaxed alignment, one key on `example.com` covers `app.example.com` and `news.example.com`. Amazon SES builds this in: when you verify a domain with DKIM, [DKIM authentication also applies to all subdomains of that domain](https://docs.aws.amazon.com/ses/latest/dg/send-email-authentication-dkim.html). Microsoft 365 is stricter and says each subdomain you send from needs its own DKIM configuration.

## The records each provider needs per domain

The number of records depends on the provider, not on how many domains you have. Repeat the provider's setup once per domain.

| Provider | Records per domain | Host | Value |
| --- | --- | --- | --- |
| [Amazon SES Easy DKIM](https://docs.aws.amazon.com/ses/latest/dg/send-email-authentication-dkim-easy.html) | 3 CNAMEs | `<token>._domainkey` | `<token>.dkim.amazonses.com` |
| [Microsoft 365](https://learn.microsoft.com/en-us/defender-office-365/email-authentication-dkim-configure) | 2 CNAMEs | `selector1._domainkey`, `selector2._domainkey` | Shown in the Defender portal for that domain |
| [Google Workspace](https://knowledge.workspace.google.com/admin/security/set-up-dkim) | 1 TXT | `google._domainkey` | The key generated for that domain |
| Self-hosted OpenDKIM | 1 TXT per selector | `<selector>._domainkey` | The `.txt` file `opendkim-genkey` writes |

### Amazon SES

Each domain is a separate identity, and each identity gets its own three tokens. In the console, go to **Configuration > Identities > Create identity**, choose **Domain**, and keep **Easy DKIM** with the default **RSA_2048_BIT** key. SES then shows three CNAMEs in the form `<token>._domainkey.yourdomain` pointing to `<token>.dkim.amazonses.com`. The same thing through the AWS SDK for JavaScript v3, looped over several domains:

```js
import { SESv2Client, CreateEmailIdentityCommand } from "@aws-sdk/client-sesv2";

const ses = new SESv2Client({ region: "us-east-1" });
const domains = ["product-a.com", "product-b.com", "product-c.io"];

for (const domain of domains) {
  const { DkimAttributes } = await ses.send(
    new CreateEmailIdentityCommand({
      EmailIdentity: domain,
      DkimSigningAttributes: { NextSigningKeyLength: "RSA_2048_BIT" },
    })
  );
  for (const token of DkimAttributes?.Tokens ?? []) {
    console.log(`${domain}  CNAME  ${token}._domainkey  ${token}.dkim.amazonses.com`);
  }
}
```

Two SES details to plan for. DKIM setup is per Region: [if you send from more than one Region, you complete DKIM in each of them](https://docs.aws.amazon.com/ses/latest/dg/send-email-authentication-dkim.html) (or use Deterministic Easy DKIM to replicate an identity's signing setup). And verification is not instant: SES says DNS detection can take up to 72 hours.

### Microsoft 365

Add each custom domain to Microsoft 365 first, then open the **DKIM** tab at **Email authentication settings** in the Defender portal. Every custom domain needs its own two CNAMEs at `selector1._domainkey` and `selector2._domainkey`. The hostnames are the same in every tenant, but the targets embed the domain with dashes (`contoso.com` becomes `contoso-com`), so copy the values for each domain from the portal or from `Get-DkimSigningConfig`. Once the CNAMEs resolve, switch the domain's toggle to **Enabled**.

### Google Workspace

In the Admin console, go to **Menu > Apps > Google Workspace > Gmail > Authenticate email**, pick the domain from the list, and choose **Generate New Record**. Google's documentation says you [must get a unique DKIM key for each domain](https://knowledge.workspace.google.com/admin/security/set-up-dkim), so repeat this for every domain in the dropdown. The default selector is `google`, which is fine to reuse on every domain for the reason above.

### Several domains on one Postfix server (OpenDKIM)

The quick fix you'll find in forum threads is listing every domain on OpenDKIM's `Domain` line. That signs all of them with one shared key. A key per domain takes two lookup tables instead. In `/etc/opendkim.conf`:

```txt
KeyTable      /etc/opendkim/KeyTable
SigningTable  refile:/etc/opendkim/SigningTable
```

Generate a 2048-bit key per domain (`opendkim-genkey` defaults to 1024 bits):

```bash
opendkim-genkey -b 2048 -d product-a.com -D /etc/opendkim/keys/product-a.com -s mail
opendkim-genkey -b 2048 -d product-b.com -D /etc/opendkim/keys/product-b.com -s mail
```

The [KeyTable](https://manpages.debian.org/bookworm/opendkim/opendkim.conf.5.en.html) maps a key name to the `d=` domain, the selector, and the private key file:

```txt
mail._domainkey.product-a.com  product-a.com:mail:/etc/opendkim/keys/product-a.com/mail.private
mail._domainkey.product-b.com  product-b.com:mail:/etc/opendkim/keys/product-b.com/mail.private
```

The SigningTable picks a key from the From address. With `refile:`, the left side is a wildcard pattern and the first match wins:

```txt
*@product-a.com  mail._domainkey.product-a.com
*@product-b.com  mail._domainkey.product-b.com
```

Publish each domain's `mail.txt` as a TXT record at `mail._domainkey` in that domain's zone. Avoid a bare `*` pattern: it signs mail for any domain, including ones with no published key.

## Worked example: three products on one Google Workspace account

Say you run `acme.dev` (company mail and product email), `acmeforms.com`, and `acmeinvoice.io`. All three have mailboxes in one Google Workspace account and send app email through Amazon SES. That is two senders on each of three domains:

| Domain | Google Workspace | Amazon SES |
| --- | --- | --- |
| `acme.dev` | TXT `google._domainkey` | 3 CNAMEs `<token>._domainkey` |
| `acmeforms.com` | TXT `google._domainkey` (its own key) | 3 different CNAMEs |
| `acmeinvoice.io` | TXT `google._domainkey` (its own key) | 3 different CNAMEs |

Twelve DKIM records in total, and no two of them share a DNS name. Google's three `google._domainkey` records hold three different keys. SES's nine tokens are unique per identity. If `acmeforms.com` later adds a newsletter tool, that tool generates a key and selector for `acmeforms.com` only, and the other two domains stay untouched.

> **DKIM checked on every domain, twice a day** Mailmark re-checks SPF, DKIM, DMARC, and blacklist status for each of your domains every 12 hours, so a CNAME deleted during a DNS cleanup on one product shows up before its mail starts failing. [Monitor your domains](https://www.mailmark.dev/?utm_source=guide&utm_medium=seo&utm_campaign=dkim-multiple-domains&utm_content=middle)

## Check every domain's keys with one script

Once records are live, check them all from one list. This script uses only Node built-ins (Node 18 or newer). `resolveTxt` follows CNAMEs, so SES and Microsoft 365 selectors work too. It reports whether each selector has a usable key and the RSA key length.

```js
// dkim-check.mjs: node dkim-check.mjs
import { resolveTxt } from "node:dns/promises";
import { createPublicKey } from "node:crypto";

// Every domain you put in a From address, and the selectors its senders use.
const domains = {
  "acme.dev": ["google", "abcdefexampletoken1"],
  "acmeforms.com": ["google", "abcdefexampletoken2"],
};

function parseTags(record) {
  return Object.fromEntries(
    record
      .split(";")
      .map((tag) => tag.trim().split("="))
      .filter(([name]) => name)
      .map(([name, ...value]) => [name.trim(), value.join("=").replace(/\s+/g, "")])
  );
}

async function checkSelector(domain, selector) {
  const name = `${selector}._domainkey.${domain}`;
  try {
    const record = (await resolveTxt(name)).map((chunks) => chunks.join("")).join("");
    const tags = parseTags(record);
    if (!tags.p) return `${name}: no usable key (p= is empty or missing)`;
    if (tags.k === "ed25519") return `${name}: ok, ed25519`;
    const key = createPublicKey({ key: Buffer.from(tags.p, "base64"), format: "der", type: "spki" });
    return `${name}: ok, ${key.asymmetricKeyDetails.modulusLength}-bit RSA`;
  } catch (err) {
    return `${name}: ${err.code ?? err.message}`;
  }
}

for (const [domain, selectors] of Object.entries(domains)) {
  for (const selector of selectors) console.log(await checkSelector(domain, selector));
}
```

Replace the example tokens with the ones your providers gave you. An empty `p=` means the key was revoked on purpose, which is normal for an old selector after rotation. `ENOTFOUND` means the record isn't published, or is published under the wrong name.

If you don't know a domain's selectors, the free [email deliverability checker](/tools/email-deliverability-checker) tries common ones (`google`, `selector1`, `selector2`, `default`, and others) along with SPF, DMARC, MX, and blacklists. For the definitive answer, send a message and read the `s=` and `d=` tags in its `DKIM-Signature` header.

## Troubleshooting DKIM across domains

**A Mailmark domain shows DKIM pending.** Each of the three SES CNAMEs is checked separately on the domain page, so the one marked missing is the one to fix. The [domain setup docs](/docs/domain-setup) cover the full record set.

**DKIM passes, DMARC fails.** Look at `header.d=` in the `Authentication-Results` header (Gmail: **Show original**). If it shows your provider's domain or a different product's domain, that domain's DKIM isn't set up in the service that sent it. Set it up for the From domain specifically. [How SPF, DKIM, and DMARC work together](/blog/understanding-spf-dkim-and-dmarc) explains alignment in more detail.

**The record was added to the wrong zone.** Most DNS dashboards append the zone name automatically, so entering `google._domainkey.acmeforms.com` inside the `acmeforms.com` zone creates `google._domainkey.acmeforms.com.acmeforms.com`. Enter only `google._domainkey`, then confirm with the script.

**A long key was rejected or truncated.** A 2048-bit key doesn't fit in one 255-character TXT string. RFC 6376 lets the record be split into several strings that receivers join back together ([section 3.6.2.2](https://datatracker.ietf.org/doc/html/rfc6376#section-3.6.2.2)), and most DNS hosts split it for you. If yours doesn't, paste the value as quoted chunks, or use 1024 bits there. SES Easy DKIM avoids this because the key lives behind a CNAME.

**Some mail from a domain is signed and some isn't.** A second service is sending as that domain without its own DKIM. Every tool that sends From the domain needs its own key and selector on it. This often turns up right after adding a support desk or billing tool.

**A new SES Region sends unsigned mail.** DKIM is per Region in SES. Set it up again in the new Region, or replicate the identity with Deterministic Easy DKIM.

If DKIM aligns everywhere and mail still lands in spam, the cause is usually reputation or content: see [why emails go to spam](/blog/why-emails-land-in-spam). For the rest of the records each sending domain needs, see [DNS records for email](/blog/dns-setup).

## Checklist for each new domain

- Add the domain to every service that will send From it, not just the first one.
- Use 2048-bit keys where the provider and DNS host allow it.
- Publish each provider's records in that domain's own zone, hostname only.
- Enable signing in the provider after the records resolve.
- Add a DMARC record to the domain (`p=none` to start) so you get reports on what signs as it.
- Send a test from the domain and confirm `header.d=` matches the From domain or its parent.
- Add the domain and its selectors to your check script.

## Frequently asked questions

### Can I use the same DKIM key for multiple domains?

Technically yes: publish the same public key under a selector on each domain and sign each domain's mail with d= set to that domain. It works, but one leaked private key then lets someone sign as every domain at once. Google Workspace, Microsoft 365, and Amazon SES all generate a separate key per domain, and self-hosted setups should too.

### Can I use the same DKIM selector name on different domains?

Yes. The public key is looked up at selector._domainkey.domain, so google._domainkey.a.com and google._domainkey.b.com are unrelated records. Selectors only need to be unique within one domain.

### Do subdomains need their own DKIM keys?

Not for DMARC. With relaxed alignment, the default, a signature with d=example.com aligns with a From address on news.example.com because both share the organizational domain. Amazon SES applies a verified domain's DKIM to all of its subdomains automatically. Microsoft 365 asks for a separate DKIM setup for each subdomain you send from.

### Why does DMARC fail when DKIM passes?

DKIM passes for whatever domain is in the signature's d= tag. DMARC only counts it when that domain shares an organizational domain with the From address. Mail from you@product-b.com signed with d=product-a.com, or with your email provider's own domain, passes DKIM and fails DMARC alignment.

### How many DKIM records does each domain need?

It depends on the provider, not the number of domains: 3 CNAMEs per domain for Amazon SES Easy DKIM, 2 CNAMEs per domain for Microsoft 365, and 1 TXT record per domain for Google Workspace. Each sending service on a domain adds its own selector.

> **Every product domain in one dashboard** Mailboxes, 30-day inbox warming, campaigns, and SPF, DKIM, and DMARC checks for all your domains, on SES in Mailmark's AWS account or your own. [Try Mailmark free](https://www.mailmark.dev/?utm_source=guide&utm_medium=seo&utm_campaign=dkim-multiple-domains&utm_content=end)


Published 2026-11-01. Updated 2026-11-01. 7 min read.

---

Mailmark - email hosting and campaigns for your own domain. Canonical page: https://www.mailmark.dev/blog/dkim-multiple-domains | [llms.txt](https://www.mailmark.dev/llms.txt) | [OpenAPI](https://www.mailmark.dev/openapi.json) | [sitemap](https://www.mailmark.dev/sitemap.xml)
