I have a server with postfix MTA installed that accepts mail for my domain, hereinafter @example.com. I need to setup forwarding for certain emails@example.com to other emails . e.g. @gmail.com and this needs to be done using SMTP transport via SES.

I have SES production access and login/password pair, that is active and has IAM permissions, I checked. I have confirmed certain From: address at SES. If I send email with these SMTP username/password directly via SES from a PHP script, everything works fine. This means that the login/password pair and From: are alright.

Now I need to rewrite From: for the emails that are going from outside and need to be forwarded. For example, an email is sent from @gmail.com -> user@example.com. The entry user is set up in virtual_alias_maps and should go to another @gmail.com. In order to rewrite From: header, the following smtp_header_checks is used:

smtp_header_checks = pcre:/etc/postfix/header_checks

the content of /etc/postfix/header_checks is following:

/^From:(.*)/ REPLACE From: "$1" <root@example.com>

root@example.com is confirmed at SES. This is the same email I'm using when testing with PHP script that uses SES directly, i.e. with no header rewrite.

When I send something from @gmail to user@example.com, here is what's going on in the logs:

Apr  8 15:04:05 ip-10-191-106-25 postfix/smtpd[32545]: connect from mail-wg0-f42.google.com[]
Apr  8 15:04:06 ip-10-191-106-25 postfix/smtpd[32545]: 3252A2415D: client=mail-wg0-f42.google.com[]
Apr  8 15:04:06 ip-10-191-106-25 postfix/cleanup[32550]: 3252A2415D: message-id=<CADLOpCq4ZFR5=xqTNxPO73-tVkF63urLt_9ueGBDrLSggy29MQ@mail.gmail.com>
Apr  8 15:04:06 ip-10-191-106-25 postfix/qmgr[32192]: 3252A2415D: from=<user@gmail.com>, size=1687, nrcpt=1 (queue active)
Apr  8 15:04:06 ip-10-191-106-25 postfix/smtpd[32545]: disconnect from mail-wg0-f42.google.com[]
Apr  8 15:04:07 ip-10-191-106-25 postfix/smtp[32551]: 3252A2415D: replace: header From: "User" <user@gmail.com>: From: " "User" <user@gmail.com>" <root@example.com>
Apr  8 15:04:07 ip-10-191-106-25 postfix/smtp[32551]: 3252A2415D: to=<user2@gmail.com>, orig_to=<user2@example.com>, relay=email-smtp.us-west-2.amazonaws.com[]:25, delay=1.3, delays=0.22/0.03/0.57/0.43, dsn=5.0.0, status=bounced (host email-smtp.us-west-2.amazonaws.com[] said: 554 Message rejected: Email address is not verified. (in reply to end of DATA command))
Apr  8 15:04:07 ip-10-191-106-25 postfix/cleanup[32550]: 7FD75243F8: message-id=<20150408150407.7FD75243F8@example.com>
Apr  8 15:04:07 ip-10-191-106-25 postfix/qmgr[32192]: 7FD75243F8: from=<>, size=3700, nrcpt=1 (queue active)
Apr  8 15:04:07 ip-10-191-106-25 postfix/bounce[32552]: 3252A2415D: sender non-delivery notification: 7FD75243F8
Apr  8 15:04:07 ip-10-191-106-25 postfix/qmgr[32192]: 3252A2415D: removed
Apr  8 15:04:08 ip-10-191-106-25 postfix/smtp[32551]: 7FD75243F8: to=<user@gmail.com>, relay=email-smtp.us-west-2.amazonaws.com[]:25, delay=0.67, delays=0.01/0/0.59/0.07, dsn=5.0.0, status=bounced (host email-smtp.us-west-2.amazonaws.com[] said: 501 Invalid MAIL FROM address provided (in reply to MAIL FROM command))
Apr  8 15:04:08 ip-10-191-106-25 postfix/qmgr[32192]: 7FD75243F8: removed

That gives me an idea that the From: header was not actually rewritten. However, the line:

Apr  8 15:04:07 ip-10-191-106-25 postfix/smtp[32551]: 3252A2415D: replace: header From: "User" <user@gmail.com>: From: " "User" <user@gmail.com>" <root@example.com>

tells us it actually has been.

I realize that I need to view the rewritten whole mail body before it's sent to AWS server, but I have no idea how to debug it.

Here is the main.cf contents:

# See /usr/share/postfix/main.cf.dist for a commented, more complete version

# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

# TLS parameters
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.

smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = example.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = example.com, ip-10-191-106-25.ec2.internal, localhost.ec2.internal, localhost, domain2.example.com
relayhost = email-smtp.us-west-2.amazonaws.com
mynetworks = [::ffff:]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
default_transport = smtp
relay_transport = relay

smtp_sasl_auth_enable = yes
smtp_sasl_security_options = noanonymous
smtp_sasl_password_maps = hash:/etc/postfix/sasl_password
smtp_use_tls = yes
smtp_tls_security_level = encrypt
smtp_tls_note_starttls_offer = yes

smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt

smtp_header_checks = pcre:/etc/postfix/header_checks

virtual_alias_maps = hash:/etc/postfix/virtual

sender_canonical_maps = hash:/etc/postfix/canonical


2 Answers 2


Looks like your initial goal with header_checks is preserving original sender and replace it with allowed sender of Amazon SES.

The problem of your approach above is the From: header become non-standard because of multiple quotes in there.

From: " "User" <user@gmail.com>" <root@example.com>

Daniel R. Tobias mentioned this issue in his article: Dan's Mail Format Site | Headers | From/To/CC/BCC

One thing that will put you at risk of having your mail program inflict nonstandard header lines on your messages is to attempt to include quotation marks within your name, like Jesse "The Body" Ventura. If inserted directly into the header, within double quotes, you'd get "Jesse "The Body" Ventura", which actually parses into two quoted strings, "Jesse " and " Ventura", with The Body sitting in the middle with uncertain purpose.

So you can rely on this nonstandard header to bypass Amazon SES checker.

One approach to solve this problem is split the two goals above in two header_checks, header_checks and smtp_header_checks. The first header_checks will preserve the original sender in another custom header (for example X-Original-From). The second one will replace the From: header.

header_checks = pcre:/etc/postfix/first_header_checks
smtp_header_checks = pcre:/etc/postfix/second_header_checks

/^From:(.*)/ PREPEND X-Original-From: $1

/^From:(.*)/ REPLACE From: <root@example.com>

Because of this schema, X-Original-From: header will be added in every incoming email. But replacing action will be executed on outgoing email only.

Another way is using pcre to exclude quote in the original From: header. Unfortunately, I don't have any time to test some ideas right now. Maybe later... I'll update this answer with other workaround.

The envelope sender address rewriting

The above parts is still half-journey. To pass amazon SES, you need rewrite the envelope sender address too.

With the exception of addresses containing labels (see below), you must verify each email address (or the domain of the email address) that you will use as a "From" or "Return-Path" address for your messages. Until your account is out of the Amazon SES sandbox, you must also verify the email address of every recipient except for the recipients provided by the Amazon SES mailbox simulator.

See the differences between Envelope Address vs. Message Header Address in this article.

I've explained some steps to rewrite the sender in this similar threads: AWS SES: "Email address is not verified" error with Postfix relay. Basically you need put this parameter in main.cf

sender_canonical_maps = regexp:/etc/postfix/sender_canonical
sender_canonical_classes = envelope_sender
smtpd_data_restrictions = check_sender_access pcre:/etc/postfix/sender_access

In /etc/postfix/sender_canonical, add

/.*/    mysenderaddress@example.com

In /etc/postfix/sender_access, add

/(.*)/  prepend X-Envelope-MailFrom: <$1>

The /etc/postfix/sender_access is used to preserved the original envelope sender address.

  • Thanks for your answer! Unfortunately, still no luck. Here is mail.log after the proposed changes: pastebin.com/dscnMhZZ . You can see that it does in fact two passes (first with X- header prepending and then second with actual replacement). But it's still not valid. I know you'll just say I'm crazy and need to double check at SES, but I did and in fact that root@example.com is verified and I sent on behalf of it via the same SES login/password. I tried to regenerate SES login/password pair and tried with it - same result :(
    – Alexey
    Apr 9, 2015 at 9:12
  • I wanted to see the message headers and body after all replacements/rewrites are done by postfix. But I don't know the way to do this. I bet there is something with the sctual rewrite. I wanted to use tcpdump, but there is TLS when connecting to AWS.
    – Alexey
    Apr 9, 2015 at 9:14
  • See my edited answer
    – masegaloeh
    Apr 9, 2015 at 9:52
  • wow! super, it works. I only changed regexp: to pcre: (here with such a basic regexp it does not matter, I think, but PCRE are more powerful than POSIX regexp, so I decided to use the same style everywhere and changed it to PCRE). But it works now, super, many thanks!
    – Alexey
    Apr 9, 2015 at 12:19
  • There is one more minor issue: some emails are not forwarded with the following errors: 554 Transaction failed: Duplicate header 'DKIM-Signature'. or 554 Transaction failed: Duplicate header 'Delivered-To'. Do I have to rewrite all headers to empty string that are not whitelisted?
    – Alexey
    Apr 15, 2015 at 15:27

following this step its working for me able to fordward the email from inside the server to customer ->

sender_canonical_maps = regexp:/etc/postfix/sender_canonical
sender_canonical_classes = envelope_sender
smtpd_data_restrictions = check_sender_access pcre:/etc/postfix/sender_access

In /etc/postfix/sender_canonical add

/.*/    mysenderaddress@example.com

In /etc/postfix/sender_access add

/(.*)/  prepend X-Envelope-MailFrom: <$1>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.