Setting Return-Path in PHP mail()

This article is about a story of how to set the Return-Path email header.

The situation started with a simple php script which sends an email from a host to itself (for testing purpose). The actual code looks as follows while the email addresses got replaced to avoid mass spam through this article.

  $from = "";
  $to = "";
  $bcc = "";

  $header = "FROM: " . $from . "\r\n".
	"Reply-To: " . $from . "\r\n".
	"Return-Path: " . $from . "\r\n".
	"Message-ID: <" . time() . "." . $from . ">\r\n".
	"BCC: " . $bcc;

  mail($to, "The subject", "The mail body", $header);

Then checking with an email if the transmission was succesful. Great, the email has been received. Being curious of the email headers the server has sent, let’s have a look at them.

Return-Path: <>
Delivered-To: <>
Subject: The subject
Message-ID: <>
Date: Wed, 28 Jan 2013 11:34:30 +0100 (CET)

Everything seems to be fine so far except the Return-Path header which is set to an email address different to what was defined in the script. The email somehow looks like the email address of my local system user account from which I sent the email. Ok, why is this so?

It turned out that the email address was not the combination of the system user and the host’s name. Or more precisely the user in fact was the system user but the domain was wrong because there was no domain in /etc/hostname and /etc/hosts.

Then I remembered that if a git repository remains unconfigured it’s using the system email address for commit messages. So the idea is to find out if git is using the same email address than the php script and if so to find out where git is taking it from. This approach seemed easier than going through all the apache, php & postfix configuration files.

In order to gather some useful information a new git repository is first created while an empty file is then added and commited.

# mkdir -p /tmp/test && cd /tmp/test
# git init
Initialized empty Git repository in /tmp/test/.git/
# touch a
# git add a && git commit -m "test" 
[master (root-commit) 6fcb56f] test
 Committer: slopjong <>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:

    git config --global "Your Name"
    git config --global

If the identity used for this commit is wrong, you can fix it with:

    git commit --amend --author='Your Name '

 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 a

And line 7 shows us the same email address than found in the Return-Path header. On freenode — the best irc server ever — I was told that git was relying on hostname or dnsdomainname and thus I’ve explicitly run both commands.

# hostname --fqd
# dnsdomainname

We see no domain here, it’s becoming interesting at this point. Fortunately there are the tools ltrace and strace which give us detailed information about the library and system calls made in an application – in this case git.

# touch b && git add b
# strace git commit -m "test"
execve("/usr/bin/git", ["git", "commit", "-m", "test"], [/* 21 vars */]) = 0
brk(0)                                  = 0xfbf7000
uname({sys="Linux", node="", ...}) = 0
access("/etc/", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2b22b8964000
access("/etc/", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/", O_RDONLY)      = 3
munmap(0x2b22b8967000, 22102)           = 0
open("/etc/passwd", O_RDONLY|O_CLOEXEC) = 4
fcntl(4, F_GETFD)                       = 0x1 (flags FD_CLOEXEC)
lseek(4, 0, SEEK_CUR)                   = 0
fstat(4, {st_mode=S_IFREG|0644, st_size=1166, ...}) = 0
mmap(NULL, 1166, PROT_READ, MAP_SHARED, 4, 0) = 0x2b22b8967000
lseek(4, 1166, SEEK_SET)                = 1166
munmap(0x2b22b8967000, 1166)            = 0
close(4)                                = 0
open("/etc/mailname", O_RDONLY)         = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=9, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2b22b8967000
read(4, "\n", 4096)             = 9
close(4)                                = 0
write(1, "[master (root-commit) dc26ff0] t"..., 489[master (root-commit) dc26ff0] test
 Committer: slopjong 
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly:

    git config --global "Your Name"
    git config --global

If the identity used for this commit is wrong, you can fix it with:

    git commit --amend --author='Your Name '
) = 489
write(1, "\n", 1
)                       = 1

Somewhere in the middle of strace’s output we see that the file /etc/mailname has been opened. Never heard of this file and thus let’s have a look at it!

# cat /etc/mailname

Mystery solved.

On this machine postfix was first configured to use system users but then it has been reconfigured to use virtual users with virtual mail accounts. So at some point postfix must have written the domain to that file which is shared with other applications too.

Update (March 24th, 2013)

Some point seems to be at installation time, when postfix is configured. It looks like it’s copying /etc/hostname to /etc/mailname.

This issue is a bit tricky, the file /etc/mailname is debian-specific and doesn’t exist in other linux systems. If one’s working over 90% with Arch Linux or other good linux distributions the first step is definitely not to look at /etc/mailname.

To fix the issue just change its content so that the Return-Path maps to an existing and valid email address.

Or just use the quick fix

After so much effort I must tell you that the mail function in php supports additional parameters passed to the sendmail binary. sendmail is also available if you have postfix installed because postfix comes with a binary with the absolute same name and interface to not break the support of all the applications relying on sendmail.

Briefly, the option -f is responsible for the Return-Path header. From the manpage:

       sendmail - Postfix to Sendmail compatibility interface

       sendmail [option ...] [recipient ...]

       sendmail -bp

       sendmail -I

       -f sender
              Set the envelope sender address. This is the address where delivery problems are sent to. With
              Postfix versions before 2.1, the Errors-To: message header overrides the error return address.

Add the parameter and take the modified code:

  $from = "";
  $to = "";
  $bcc = "";

  $header = "FROM: " . $from . "\r\n".
	"Reply-To: " . $from . "\r\n".
	"Message-ID: <" . time() . "." . $from . ">\r\n".
	"BCC: " . $bcc;

  $param = "-f" . $from;

  mail($to, "The subject", "The mail body", $header, $param);


  1. Thank you! That solved one of my Debian problems! :)

  2. Merci beaucoup vous avez résolu mon problème sur un OVH release2 avec Postfix et PHP!

  3. slopjong

    @Jean-Luc, je vous en prie.

  4. Mal

    Many thanks your information made my day !!

  5. Kitchen designs deal at length along with all the aspects which go into making a kitchen.
    Each part of the kitchen is worked out in detail,
    by providing measurements of add-ons, in the sink to the cupboards, and the lighting.
    There are different in creating their own kitchen designs,
    designing products overly that guide a person. A person may possibly also start to see the newest kitchen lay-out of
    buddies, and try and build the kitchen as per their
    layouts and dimensions.

  6. Candy Saga, the game program provided by, is all of the rage right
    now. Several gamers have found themselves hopelessly hooked to it – crushing striped, covered, and
    dancing candies into the early hours of the night.
    We fight to locate the “energy” sweets and celebrate when we make three or maybe more like-colored candies to clear the board and proceed to another degree.

    I’ve fallen victim to the Candy Crush Soda Saga craze and am focused on seeing it
    to the end (I think nowadays there are more than 400 400 amounts,
    constituting about 30 episodes). And as I’m enjoying through all hours of the night
    only attempting to get that sweet high we Candy Crushers demand for our daily nutrition, I begin to realize
    that there are life-lessons to be learned from candy crush soda sport.
    Maybe it is the sugar high or my desperate effort to rationalize the mind -numbing hours I invest crushing sweets, but the subsequent life-lessons are
    ones we should remember.

  7. If you are starting your own personal cheap web hosting site, you may have noticed how difficult it can be to find a paypal hosting support.
    As with a lot of issues, it is just hard in the event you do not know everything you do.

  8. Fortnite war mir lange Zeit zu teuer, aer jetzt gibt
    es Fortnite für nur 11 Euros bbei G2A ,
    da musste ich zuschlagen :) Das wird ein Fest!

Trackbacks / Pings

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>