> ## 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 failure when Message-ID is added after signing

> Why a message can fail DKIM authentication when your server oversigns the Message-ID header and one is added downstream, and how to fix it.

If your mail server DKIM-signs messages but a receiving server reports `dkim=fail` even though your configuration hasn't changed, the cause may be header oversigning: your server declared `Message-ID` as a signed header while it was absent, and MailChannels inserted one afterward — invalidating the signature.

## What causes this

DKIM signatures include an `h=` tag that lists every header covered by the signature. Some mail servers — Exim in particular — include `Message-ID` in this list by default, even when the header is not present in the message at signing time. This practice is called **oversigning**: by declaring a header as "signed" while it is absent, the server ensures that any later attempt to inject that header will invalidate the signature, which protects against header injection attacks.

The problem arises when a message genuinely has no `Message-ID` at the time of signing, and one is legitimately added downstream. Because `Message-ID` was declared in the `h=` tag, inserting it after signing invalidates the signature.

Here is the sequence that triggers the failure:

1. Your application sends a message to your mail server **without a `Message-ID` header**.
2. Your mail server DKIM-signs the message. The signature's `h=` tag includes `Message-ID` — oversigned, even though the header isn't there yet.
3. Your server relays the signed message to MailChannels.
4. MailChannels detects the missing `Message-ID` and inserts one.
5. The receiving server validates the DKIM signature. Because `Message-ID` is now present but wasn't there when the signature was computed, the signature fails.

Adding a `Message-ID` to a message that has none is normal message-handling behavior, not tampering. Although `Message-ID` is syntactically optional under [RFC 5322](https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.4), the RFC says every message SHOULD have one. In practice, major receivers expect it: [Google's Email Sender Guidelines](https://support.google.com/a/answer/81126) state "Make sure every message includes a valid Message-ID," and [Yahoo's sender requirements](https://senders.yahooinc.com/best-practices/) require senders to comply with RFC 5321 and RFC 5322. Messages without a valid `Message-ID` are more likely to be rejected or treated as suspicious by receiving mail servers.

Nothing was tampered with. The failure is a side effect of oversigning a header that was absent at the time of signing.

## Will this affect email delivery?

A DKIM failure is not harmless, even when DMARC still passes through SPF alignment. DKIM is one of the authentication signals receivers use when deciding whether to accept, filter, or classify a message. A failed DKIM signature does not automatically require rejection — [RFC 6376](https://datatracker.ietf.org/doc/html/rfc6376) notes that verifying MTAs may apply local policy to unverifiable mail — but receivers may still treat it as a negative signal. It also means the message is no longer authenticated through that DKIM signature, which matters if SPF is unavailable, unaligned, or breaks in the future.

A failed DKIM signature is also worth fixing even if DMARC currently passes through SPF alignment. It leaves delivery dependent on SPF alignment and removes DKIM as a reliable authentication path. If SPF is missing, fails, or is not aligned, and no other aligned DKIM signature passes, this DKIM failure can cause DMARC to fail as well. Under `p=quarantine` or `p=reject`, receivers that consider the domain's DMARC policy may be more likely to filter, quarantine, or reject the message as part of their local policy.

## How to fix it

### Option 1: Generate Message-ID before signing (recommended)

Configure your sending application or mail server to always include a `Message-ID` header before the message reaches the DKIM signing step. Once `Message-ID` is present at signing time, it is included in the signed content and no downstream insertion is needed.

Most modern mail clients and application mail libraries generate `Message-ID` automatically. Check whether your application's mail library has an option to generate the header, and ensure it is enabled. If you are sending through your own SMTP stack, add `Message-ID` generation before the message is handed to the MTA. The important detail is ordering: generate `Message-ID` before DKIM signing, not after it.

### Option 2: Stop signing the absence of Message-ID

If you cannot control `Message-ID` generation at the application layer, adjust your DKIM signing configuration so the signer does not sign the absence of `Message-ID`.

For Exim, check the `dkim_sign_headers` option in the transport block that handles MailChannels delivery (for example, `mailchannels_smtp`). Exim's header list has three modes per entry: a bare name oversigns by default when the header is absent, a `+` prefix explicitly oversigns, and an `=` prefix signs only the header instances that are actually present — so a header added later, like a downstream `Message-ID` insertion, no longer breaks the signature. See the [Exim DKIM documentation](https://www.exim.org/exim-html-current/doc/html/spec_html/ch-dkim_spf_srs_and_dmarc.html) for the full `dkim_sign_headers` option reference. If your config lists `Message-ID` with no prefix or a `+` prefix, switching it to `=Message-ID` is the fix. If your Exim version does not support prefixes, remove `Message-ID` from the list entirely. See [DKIM configuration for cPanel](/outbound/mta/cpanel-dkim) for how to edit the Exim transport configuration.

<Note>
  Using `=Message-ID` is the better compromise: it still signs an existing `Message-ID`, so changes to that signed header will break the signature, but it does not sign the absence of `Message-ID`. Removing `Message-ID` from the list entirely means the header is unprotected by that signature.
</Note>

## Confirm this is the cause

Check the following to verify this specific issue before making changes:

1. **Was `Message-ID` missing from the original message?** Look at the raw message as sent from your server (before MailChannels received it). If no `Message-ID` header was present, that confirms one required condition for this failure.
2. **Does the DKIM signature sign the absence of `Message-ID`?** Inspect the `DKIM-Signature` header in the delivered message. If `Message-ID` was missing before signing and `message-id` appears in the `h=` tag, the server signed the absence of that header.
3. **Does the `Message-ID` in the delivered message differ from your application's normal format?** If the value looks different from what your application normally generates, or uses infrastructure-specific formatting, it may have been inserted downstream. Confirm by checking outbound logs or the raw message before it reached MailChannels.
4. **Do SPF and DMARC pass alongside `dkim=fail`?** If `spf=pass` and `dmarc=pass` appear alongside `dkim=fail`, the message may have been delivered because SPF alignment, or another aligned DKIM signature, satisfied DMARC. This pattern is consistent with the issue, but the decisive evidence is still: `Message-ID` was missing before signing, `message-id` appears in `h=`, and `Message-ID` is present after relay.
5. **Is this a header failure rather than a body hash failure?** This issue affects the signed header set, not the message body. If the receiver reports a body hash mismatch, look for body modification after signing instead.
