Fixing duplicate DKIM-Signature header with AWS SES and Rspamd
I debugged why AWS SES was bouncing outgoing emails with a 554 Duplicate header error, traced it to Rspamd DKIM signing conflicting with SES, and fixed it by disabling the milter in Postfix.
The symptom
I manage a self-hosted mail server for our AWS User Group Jakarta community. We use Billion Mail with AWS SES as the outbound relay. It had been running fine for months, then suddenly last month, emails started bouncing. No config changes. No updates. It just broke.
The Postfix queue log showed this:
<sender-email> 5.0.0 1.6 0.63/0/0.71/0.27 bounced
host email-smtp.ap-southeast-1.amazonaws.com[<aws-ses-ip>] said:
554 Transaction failed: Duplicate header 'DKIM-Signature'.
(in reply to end of DATA command)
My first thought was, "Wait, what duplicate DKIM header? Nothing changed. This was working last month." I was wrong about the "nothing changed" part, something must have changed, I just could not see it yet.
The environment
Our mail stack runs on a single EC2 instance with these Docker containers:
- Postfix (
billionmail/postfix:1.6) as the MTA - Rspamd (
billionmail/rspamd:1.2) as the milter for spam filtering and DKIM signing - Dovecot for IMAP and SASL authentication
- AWS SES as the SMTP relay in the
ap-southeast-1region - The SES relay is configured via the Billion Mail web console, which stores the relay mapping in PostgreSQL
The relay config maps our domain to email-smtp.ap-southeast-1.amazonaws.com:587 with SASL authentication.
The investigation
I started by checking the Postfix main.cf inside the container. The milter configuration looked like this:
# dovecot rspamd
smtpd_sasl_type = dovecot
smtpd_sasl_authenticated_header = yes
smtpd_sasl_path = inet:dovecot:10002
smtpd_sasl_auth_enable = yes
virtual_transport = lmtp:inet:dovecot:26
smtpd_milters = inet:rspamd:11332
non_smtpd_milters = inet:rspamd:11332
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
smtpd_use_tls = yes
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
milter_protocol = 6
milter_default_action = accept
The smtpd_milters and non_smtpd_milters lines tell Postfix to pipe every email through Rspamd. That includes both incoming and outgoing mail.
Then I checked the Rspamd DKIM signing config at /etc/rspamd/local.d/dkim_signing.conf:
sign_headers = "from:sender:reply-to:subject:date:message-id:to:cc:mime-version:content-type:content-transfer-encoding:content-language:resent-to:resent-cc:resent-from:resent-sender:resent-message-id:in-reply-to:references:list-id:list-help:list-owner:list-unsubscribe:list-subscribe:list-post:list-unsubscribe-post:disposition-notification-to:disposition-notification-options:original-recipient:openpgp:autocrypt";
use_esld = false;
domain {
#BT_DOMAIN_DKIM_BEGIN
#<our-domain>_DKIM_BEGIN
<our-domain> {
selectors [
{
path: "/var/lib/rspamd/dkim/<our-domain>/default.private";
selector: "default";
},
{
path: "/var/lib/rspamd/dkim/<our-domain>/short.private";
selector: "short";
}
]
}
#<our-domain>_DKIM_END
#BT_DOMAIN_DKIM_END
}
And there it was. Rspamd was configured to DKIM-sign every outgoing email from our domain with two selectors, default and short.
The root cause
Here is the flow of what was happening:
- A user sends an email through Roundcube (or an MUA)
- Postfix receives it and pushes it through the Rspamd milter
- Rspamd adds a
DKIM-Signatureheader (or two, since there are two selectors) for our domain - Postfix then forwards the email to AWS SES via the relay config
- SES sees the email already has a
DKIM-Signatureheader and rejects it
The error message is a bit misleading. SES does not actually add its own DKIM signature when sending via SMTP credentials (it only does that with Easy DKIM via the API). But SES still validates the email structure and refuses to relay messages that have pre-existing DKIM-Signature headers. The exact reason is not fully documented by AWS, but the behavior is consistent, other people have hit this with Postfix and SES too.
The core problem is that both Rspamd and SES "want" to be the one signing your emails. When Rspamd signs first, SES sees a duplicate and bounces the message.
Honestly, I still do not know why this worked before and suddenly broke. The config had not changed. My best guess is that AWS SES tightened its header validation on their side, maybe in a silent update. But I could not find any announcement about it. If you know what actually triggered this, I would love to hear about it.
The fix
I had two options.
Option A: Remove the DKIM domain block from Rspamd's config and let SES handle DKIM signing via Easy DKIM. This is the cleaner approach because it preserves Rspamd's spam filtering while only disabling the signing part.
Option B: Completely disable the Rspamd milter in Postfix. This is the nuclear option, it turns off everything Rspamd does, including spam filtering. But it is simple and it works immediately.
I went with Option B because I did not want to set up Easy DKIM in SES right now. Here is the change I made to the Postfix main.cf:
# dovecot (milter disabled to stop DKIM conflict with SES)
smtpd_sasl_type = dovecot
smtpd_sasl_authenticated_header = yes
smtpd_sasl_path = inet:dovecot:10002
smtpd_sasl_auth_enable = yes
virtual_transport = lmtp:inet:dovecot:26
# Milters disabled
#smtpd_milters = inet:rspamd:11332
#non_smtpd_milters = inet:rspamd:11332
smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination
smtpd_use_tls = yes
# Milter params commented out since milter is off
#milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
#milter_protocol = 6
#milter_default_action = accept
I applied this through the Billion Mail web console, under Settings, Service, Edit config of the Postfix container. Then I reloaded Postfix and tried sending again. The email went through immediately.

This workaround came from a GitHub issue on the BillionMail repository, where someone else hit the same problem with SES and Rspamd.
What I gave up
By disabling the milter entirely, I also turned off Rspamd's spam filtering, virus scanning, and reputation checks. For now that is fine because our community mail server has a small number of users and we do not receive much spam. But if you have multiple users or public-facing addresses, you should probably go with Option A instead, just remove the DKIM domain block from dkim_signing.conf and restart Rspamd.