> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mailchannels.com/llms.txt
> Use this file to discover all available pages before exploring further.

# DKIM signing and key management

> MailChannels manages DKIM signing and key rotation for you.

## DKIM

You can choose to manage your own keys, or let MailChannels manage your keys for you. If you choose to manage your own keys,
you are responsible for creating the key pair, publishing the DNS record, and rotating keys when necessary. If you choose
to let MailChannels manage your keys, we manage the keys, and all you have to do is update the DNS.

**If you want to manage your own keys:** include `dkim_domain`, `dkim_selector` and `dkim_private_key` in your API request
to send a message.

**If you want MailChannels to manage your keys:** MailChannels generates and stores the private key, and handles the signing
process.

### Create a key pair

Generate a key pair for your domain. `selector` is a string that identifies the key and is used in the DNS record and email headers.
`algorithm` defaults to `rsa` and `key_length` defaults to `2048`. Choose a short, descriptive selector that helps you
identify the key, for example "marketing2026" or "transactional20260101".

<CodeGroup>
  ```bash cURL theme={null}
  #!/usr/bin/env bash
  # Create a DKIM key pair managed by MailChannels. The response includes the
  # public key and the DNS TXT record to publish.
    set -u
    : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}"
    : "${DOMAIN:?Set DOMAIN (the sending domain, e.g. example.com)}"
    : "${SELECTOR:?Set SELECTOR (e.g. mc1)}"

  curl -X POST "https://api.mailchannels.net/tx/v1/domains/$DOMAIN/dkim-keys" \
    -H "Content-Type: application/json" \
    -H "X-Api-Key: $MAILCHANNELS_API_KEY" \
    -d @- <<JSON
  {
    "selector": "$SELECTOR",
    "algorithm": "rsa",
    "key_length": 2048
  }
  JSON
  ```

  ```javascript Node.js theme={null}
  import { MailChannels } from "mailchannels-sdk";

  const { MAILCHANNELS_API_KEY, DOMAIN, SELECTOR } = process.env;
  if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running");
  if (!DOMAIN) throw new Error("Set DOMAIN (the sending domain, e.g. example.com)");
  if (!SELECTOR) throw new Error("Set SELECTOR (e.g. mc1)");

  const mailchannels = new MailChannels(MAILCHANNELS_API_KEY);

  const { data, error } = await mailchannels.domains.dkim.create(DOMAIN, {
    selector: SELECTOR,
    algorithm: "rsa",
    length: 2048,
  });

  if (error) {
    console.error("Create DKIM key failed:", error);
    process.exit(1);
  }
  console.log(data);
  ```

  ```python Python theme={null}
  import os
  import sys

  import mailchannels

  if not os.environ.get("MAILCHANNELS_API_KEY"):
      sys.exit("Set MAILCHANNELS_API_KEY before running")

  domain = os.environ.get("DOMAIN")
  selector = os.environ.get("SELECTOR")
  if not domain:
      sys.exit("Set DOMAIN (the sending domain, e.g. example.com)")
  if not selector:
      sys.exit("Set SELECTOR (e.g. mc1)")

  # Create a MailChannels-managed DKIM key pair. The response includes the
  # public key and the DNS TXT record to publish.
  response = mailchannels.Dkim.create(
      domain,
      selector=selector,
      algorithm="rsa",
      key_length=2048,
  )

  print(response)
  ```

  ```php PHP theme={null}
  <?php

  require_once __DIR__ . '/vendor/autoload.php';

  use MailChannels\Client;

  $api_key  = getenv('MAILCHANNELS_API_KEY') or exit("Error: MAILCHANNELS_API_KEY is not set\n");
  $domain   = getenv('DOMAIN') or exit("Error: DOMAIN is not set\n");
  $selector = getenv('SELECTOR') or exit("Error: SELECTOR is not set\n");

  $client = new Client(apiKey: $api_key);

  // Create a MailChannels-managed DKIM key pair. The response includes the
  // public key and the DNS TXT record to publish.
  $response = $client->dkim->create(
      domain:    $domain,
      selector:  $selector,
      algorithm: 'rsa',
      keyLength: 2048,
  );

  print_r($response->toArray());
  ```
</CodeGroup>

The response includes the suggested DNS record under `dkim_dns_records`:

<CodeGroup>
  ```json Response theme={null}
  {
    "domain": "example.com",
    "selector": "mc1",
    "public_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...",
    "status": "active",
    "algorithm": "rsa",
    "key_length": 2048,
    "created_at": "2026-05-13T18:30:00Z",
    "dkim_dns_records": [
      {
        "name": "mc1._domainkey.example.com",
        "type": "TXT",
        "value": "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."
      }
    ]
  }
  ```
</CodeGroup>

### Publish the DNS record

Add the returned TXT record to your domain's DNS. Receivers fetch this record to verify the DKIM signatures on your mail.
Here is an example record:

DNS Name: `mc1._domainkey.example.com`

TXT Record value: `v=DKIM1; k=rsa; p=public_key_value`

The format of the record is specified in [RFC 6376, section 3.6.1](https://datatracker.ietf.org/doc/html/rfc6376#section-3.6.1).

### Send a message using the managed key

Once you have created a key and published the DNS record, your mail will automatically be signed using that key. If you have
multiple keys and want to specify which one to use, include `dkim_selector` in your send request.
See the [API reference](/api-reference/send/send-an-email) for more details.

<CodeGroup>
  ```bash cURL theme={null}
  #!/usr/bin/env bash
  # Send a message signed with a MailChannels-managed DKIM key. Optionally set
  # dkim_selector; the private key stays with MailChannels.

    set -u
    : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}"
    : "${FROM_EMAIL:?Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)}"
    : "${TO_EMAIL:?Set TO_EMAIL}"
    : "${DKIM_SELECTOR:?Set DKIM_SELECTOR (the selector of an active key)}"

  curl -X POST https://api.mailchannels.net/tx/v1/send \
    -H "Content-Type: application/json" \
    -H "X-Api-Key: $MAILCHANNELS_API_KEY" \
    -d @- <<JSON
  {
    "personalizations": [
      { "to": [{ "email": "$TO_EMAIL" }] }
    ],
    "from": { "email": "$FROM_EMAIL" },
    "subject": "Hello from MailChannels",
    "content": [
      { "type": "text/plain", "value": "Signed with a MailChannels-managed DKIM key." }
    ],
    "dkim_selector": "$DKIM_SELECTOR"
  }
  JSON
  ```

  ```javascript Node.js theme={null}
  // Send a message signed with a MailChannels-managed DKIM key. Set
  // `dkim.selector` to choose a specific active key; the private key stays with
  // MailChannels.
  import { MailChannels } from "mailchannels-sdk";

  const { MAILCHANNELS_API_KEY, FROM_EMAIL, TO_EMAIL, DKIM_SELECTOR } = process.env;
  if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running");
  if (!FROM_EMAIL) throw new Error("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)");
  if (!TO_EMAIL) throw new Error("Set TO_EMAIL");
  if (!DKIM_SELECTOR) throw new Error("Set DKIM_SELECTOR (the selector of an active key)");

  const mailchannels = new MailChannels(MAILCHANNELS_API_KEY);

  const { data, error } = await mailchannels.emails.send({
    from: { email: FROM_EMAIL },
    to: { email: TO_EMAIL },
    subject: "Hello from MailChannels",
    text: "Signed with a MailChannels-managed DKIM key.",
    dkim: { selector: DKIM_SELECTOR },
  });

  if (error) {
    console.error("Send failed:", error);
    process.exit(1);
  }
  console.log(data);
  ```

  ```python Python theme={null}
  import os
  import sys

  import mailchannels

  if not os.environ.get("MAILCHANNELS_API_KEY"):
      sys.exit("Set MAILCHANNELS_API_KEY before running")

  from_email = os.environ.get("FROM_EMAIL")
  to_email = os.environ.get("TO_EMAIL")
  dkim_selector = os.environ.get("DKIM_SELECTOR")
  if not from_email:
      sys.exit("Set FROM_EMAIL (must be on a Domain-Lockdown-authorized domain)")
  if not to_email:
      sys.exit("Set TO_EMAIL")
  if not dkim_selector:
      sys.exit("Set DKIM_SELECTOR (the selector of an active key)")

  # Sign the message with a MailChannels-managed DKIM key. The private key stays
  # with MailChannels; the caller only references it by selector.
  response = mailchannels.Emails.send(
      {
          "from": {"email": from_email},
          "to": [{"email": to_email}],
          "subject": "Hello from MailChannels",
          "text": "Signed with a MailChannels-managed DKIM key.",
          "dkim_selector": dkim_selector,
      }
  )

  print(response)
  ```

  ```php PHP theme={null}
  <?php

  require_once __DIR__ . '/vendor/autoload.php';

  use MailChannels\Client;

  $api_key       = getenv('MAILCHANNELS_API_KEY') or exit("Error: MAILCHANNELS_API_KEY is not set\n");
  $from_email    = getenv('FROM_EMAIL') or exit("Error: FROM_EMAIL is not set\n");
  $to_email      = getenv('TO_EMAIL') or exit("Error: TO_EMAIL is not set\n");
  $dkim_selector = getenv('DKIM_SELECTOR') or exit("Error: DKIM_SELECTOR is not set\n");

  $client = new Client(apiKey: $api_key);

  // Sign the message with a MailChannels-managed DKIM key. The private key stays
  // with MailChannels; the caller only references it by selector.
  $response = $client->emails->send([
      'from'          => ['email' => $from_email],
      'to'            => ['email' => $to_email],
      'subject'       => 'Hello from MailChannels',
      'text'          => 'Signed with a MailChannels-managed DKIM key.',
      'dkim_selector' => $dkim_selector,
  ]);

  print_r($response->toArray());
  ```
</CodeGroup>

### Rotate a key

Rotating a key refers to the process of creating a new key, updating your DNS records, and retiring the old key. This is
a security best practice that limits the potential damage if a private key is compromised. MailChannels provides a tool
to rotate your DKIM keys. When you rotate a key, a new key pair is generated, and the old key is marked as `rotated`.

<CodeGroup>
  ```bash cURL theme={null}
  #!/usr/bin/env bash
  # Rotate an active DKIM key.
    set -u
    : "${MAILCHANNELS_API_KEY:?Set MAILCHANNELS_API_KEY before running}"
    : "${DOMAIN:?Set DOMAIN (the sending domain, e.g. example.com)}"
    : "${CURRENT_SELECTOR:?Set CURRENT_SELECTOR (the active key to rotate out)}"
    : "${NEW_SELECTOR:?Set NEW_SELECTOR (the selector for the new active key)}"

  curl -X POST "https://api.mailchannels.net/tx/v1/domains/$DOMAIN/dkim-keys/$CURRENT_SELECTOR/rotate" \
    -H "Content-Type: application/json" \
    -H "X-Api-Key: $MAILCHANNELS_API_KEY" \
    -d @- <<JSON
  {
    "new_key": { "selector": "$NEW_SELECTOR" }
  }
  JSON
  ```

  ```javascript Node.js theme={null}
  import { MailChannels } from "mailchannels-sdk";

  const { MAILCHANNELS_API_KEY, DOMAIN, CURRENT_SELECTOR, NEW_SELECTOR } = process.env;
  if (!MAILCHANNELS_API_KEY) throw new Error("Set MAILCHANNELS_API_KEY before running");
  if (!DOMAIN) throw new Error("Set DOMAIN (the sending domain, e.g. example.com)");
  if (!CURRENT_SELECTOR) throw new Error("Set CURRENT_SELECTOR (the active key to rotate out)");
  if (!NEW_SELECTOR) throw new Error("Set NEW_SELECTOR (the selector for the new active key)");

  const mailchannels = new MailChannels(MAILCHANNELS_API_KEY);

  const { data, error } = await mailchannels.domains.dkim.rotate(DOMAIN, CURRENT_SELECTOR, {
    newKey: { selector: NEW_SELECTOR },
  });

  if (error) {
    console.error("Rotate DKIM key failed:", error);
    process.exit(1);
  }
  console.log(data);
  ```

  ```python Python theme={null}
  import os
  import sys

  import mailchannels

  if not os.environ.get("MAILCHANNELS_API_KEY"):
      sys.exit("Set MAILCHANNELS_API_KEY before running")

  domain = os.environ.get("DOMAIN")
  current_selector = os.environ.get("CURRENT_SELECTOR")
  new_selector = os.environ.get("NEW_SELECTOR")
  if not domain:
      sys.exit("Set DOMAIN (the sending domain, e.g. example.com)")
  if not current_selector:
      sys.exit("Set CURRENT_SELECTOR (the active key to rotate out)")
  if not new_selector:
      sys.exit("Set NEW_SELECTOR (the selector for the new active key)")

  response = mailchannels.Dkim.rotate(
      domain,
      current_selector,
      new_selector=new_selector,
  )

  print(response)
  ```

  ```php PHP theme={null}
  <?php

  require_once __DIR__ . '/vendor/autoload.php';

  use MailChannels\Client;

  $api_key          = getenv('MAILCHANNELS_API_KEY') or exit("Error: MAILCHANNELS_API_KEY is not set\n");
  $domain           = getenv('DOMAIN') or exit("Error: DOMAIN is not set\n");
  $current_selector = getenv('CURRENT_SELECTOR') or exit("Error: CURRENT_SELECTOR is not set\n");
  $new_selector     = getenv('NEW_SELECTOR') or exit("Error: NEW_SELECTOR is not set\n");

  $client = new Client(apiKey: $api_key);

  $response = $client->dkim->rotate($domain, $current_selector, $new_selector);

  print_r($response->toArray());
  ```
</CodeGroup>

The response contains both keys, each with its suggested DNS record:

<CodeGroup>
  ```json Response theme={null}
  {
    "new_key": {
      "domain": "example.com",
      "selector": "mc2",
      "public_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...new...",
      "status": "active",
      "algorithm": "rsa",
      "key_length": 2048,
      "created_at": "2026-05-13T18:30:00Z",
      "dkim_dns_records": [
        {
          "name": "mc2._domainkey.example.com",
          "type": "TXT",
          "value": "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...new..."
        }
      ]
    },
    "rotated_key": {
      "domain": "example.com",
      "selector": "mc1",
      "public_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...",
      "status": "rotated",
      "algorithm": "rsa",
      "key_length": 2048,
      "status_modified_at": "2026-05-13T18:30:00Z",
      "gracePeriodExpiresAt": "2026-05-16T18:30:00Z",
      "retiresAt": "2026-05-27T18:30:00Z",
      "dkim_dns_records": [
        {
          "name": "mc1._domainkey.example.com",
          "type": "TXT",
          "value": "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."
        }
      ]
    }
  }
  ```
</CodeGroup>

The rotated key remains valid for signing for a **3-day grace period** (`gracePeriodExpiresAt`) and is automatically
marked `retired` 2 weeks after rotation (`retiresAt`). To avoid signature-verification failures during the cutover:

1. Publish the new key's DNS record **before** updating your send requests to use the new selector.
2. Leave the rotated key's DNS record in place until the grace period ends — in-flight mail signed with it still
   needs the record for verification.
3. Once the grace period ends, update your send requests to use the new selector. Signing with the old selector
   will fail after it transitions to `retired`.
