Bouncing incoming email over to a snippet of PHP or other server-side script is the quickest way to accomplish quite a few potentially tricky tasks. On OS X Server, you can do it with the same simple tried-and-true method that allows one-to-many distribution of incoming emails.
Every now and again, you might need to process incoming email with a PHP or other server-side script; for example, on one of my sites, I’ve used a server-side script to process incoming email questions and automatically allocate them across a set of people available to respond to them. On WHM/cPanel, this is supported with a simple “Pipe to a Program” setting under the “Advanced Options” on the “Add a New Forwarder” cPanel screen. OS X Server provides no such piping option directly in Server.app: it will complain if you attempt to enter anything other than a valid email address for forwarding under a user’s “Edit Mail Options”.
Ancient History to the Rescue
Fortunately, it’s still entirely possible to pipe email to a script, but in my own case, it took quite awhile to figure out how. I tried many variations on
postfix tweaks and hacks to get piping to work, but although it was possible to get things partially working this way, it rapidly became clear that this approach was unnecessarily complicated, brittle, and prone to error. The ‘real’ answer is both remarkably simple and extremely difficult to find unless you already know (and remember!) where to look. If you’ve used Unix systems for awhile, you might remember that way back in the dark ages — long before the advent of web-based interfaces for these things — it was common to use a
.forward file to temporarily alter where your incoming email was going. It turns out that despite all the fancy
postfix alias handling at work under the hood, OS X still respects the presence of a simple
.forward file placed in a user’s home directory.
So, if you need server-side email processing via a PHP or other script, the process is very simple. Start by creating a user with a full home directory and email set to be stored locally. Yes, even though you’re going to be piping email to a script, you still need to specify that the email will be stored locally — and it can be if you wish, in addition to being piped. Now drop your script into that user’s home directory or somewhere else handy, giving it 755 permissions. Finally, add a
.forward file to that user’s home directory that specifies a pipe with the full path to the script, like so:
Note that this will not work if you tell Server.app that email should be forwarded to another address: in that case, neither the
.forward nor the forward you specify in Server.app will have any effect.
If you’d like also to keep a copy of the incoming message, as well as forwarding it, you do so by adding the account’s own ID to the .forward, but prefixed with a backslash:
(Note that you should use just the actual account name, rather than the full email address, when retaining a copy of the incoming message, to avoid creating a forwarding loop; more on forwarding loops in a moment.)
You can apply this same method to forward email to any number of different places, separating each destination with a comma or line break. As far as I’m aware, forwarding to multiple destinations cannot be done through Server.app (although it is perfectly easy to set multiple incoming aliases by simply listing them for the user under “Advanced Options…”).
That’s all there is to it — done.
Avoiding Forwarding Loops
I cannot explain why this occurs, and it’s probably down to a mistake somewhere on my part, but I’ve found that
postfix may complain about a forwarding loop under some configurations where the processing script sends emails out again from the same domain — even though there does not appear to be any loop at all. For example, this error might arise if your processor lives at
firstname.lastname@example.org and sends emails out with a return path of
email@example.com, even though there may be no other incoming connection between
Note that even though I’m not sure why
postfix would complain initially, even if it did not, there’s still a problem with sending outgoing messages with a return path equal to that of your processing script: if another server bounces a message back to your processing script for any reason, it will get processed again. And if it sends something out as a result which gets bounced again, it will process it again. This path leads to much unhappiness.
If you do encounter difficulties with forwarding loops — or if you just want to avoid loops being created by other servers — the easy solution is just to set the processing script itself to live on the server’s main domain but to interact with it via another services-only account set up on a different, public-facing domain for the sole purpose of forwarding email on to the script’s account. You can still set the return path of any emails which your handler sends out to match the public-facing domain. This is in keeping with the general philosophy I outlined in an earlier article of reserving the main domain for admin-type purposes and using other domains for all the direct interaction with the rest of the outside world. (See “Basic Configuration and Preparing the Way for Extra Software OS X Server Needs”.) So although you can still access the processing script via any domain you like using forwarding accounts set up in Server.app, ensuring that the processing script’s account itself lives at a different domain, such as the server’s main domain, appears to be the easiest way to avoid chasing phantom forwarding loops.
For example, the arrangement I originally described above could be made to work by creating a new account with an address of
firstname.lastname@example.org, where ‘server.com’ is your server-only domain and turning
email@example.com into a services-only account whose email gets forwarded to
firstname.lastname@example.org. That way, to the external world, interactions still happen entirely with
email@example.com, but it’s actually the script activated by a
.forward file living at the account of
firstname.lastname@example.org which does all the heavy lifting.
All material on this site is carefully reviewed, but its accuracy cannot be guaranteed, and some suggestions offered here might just be silly ideas. For best results, please do your own checking and verifying. This specific article was last reviewed or updated by Greg on .