Piping email to a script with cPanel

Note: Found a better solution to some of the annoyances below detailed in a later blog-post.

I recently found myself wanting to read email from an address, parse it and act upon it giving me an entrypoint to a very simple API from my Nokia e71’s native email facilities. My host uses cPanel for administration, and it helpfully has the ability to pipe mail destined for an email address to an arbitrary script file.

Given it took a little effort, a few different web resources and some annoyances I’ve documented the steps here.

Setup your processing script

Your processing script can be anything that can be run by the shell on your host. My application required a PHP end-point so these instructions are for a PHP script.

First off, create a new PHP file. Unlike PHP files you’ve created to be accessed from the web, we’ll need a shebang line at the top pointing to the executable to be used to process the script. On my host, that makes the very initial script the following:

#!/usr/bin/php -q
<?php
?>

Notice that -q? By default, invoking the PHP binary will render HTTP headers to the output stream. As we’re going to see shortly, any output generated by your processing script will be seen as a delivery failure by the mailer so we want to avoid shoving those headers into the output. The -q switch turns off those headers.

The mailer will be supplying the full text of the email (replete with headers) to STDIN – we can read in the email by reading from php://stdin using fopen and the standard PHP file access functions. Handily a chap called webignition already produced a function to process STDIN and yield us a string in a forum post.

However, that string’s pretty much just raw protocol – it’d be easier if we could access the email’s contents in a more structured way. Taking my cue from a Stack Overflow post I decided upon using the Plancake email parser class available in this Git repo. Using the class is straightforward – we construct a PlancakeEmailParser object passing in the string yielded by mailRead. This exposes the contents of the email through accessor methods on the object.

So our recipe so far:

  • Create a PHP script with a shebang line that points to the PHP binary and contains the -q switch to suppress HTTP header output
  • Read the contents of STDIN and pass it as a string to the constructor of PlancakeEmailParser giving us structured email data
  • <Do something with the structured email data>

That last bit’s on you.

Setup forwarding on your desired address

Let’s say that the address you want to use is api@example.com.

  • Log into cPanel and fire up the Forwarders page in the Mail group
  • Hit ‘Add Forwarder’ – you’ll be taken to the Add New Forwarder page
  • Enter the email account name in the ‘Address to forward’ box (and pick the correct domain if your cPanel’s set up to manage multiple)
  • Click the Advanced Options link to expand the form out
  • Pick the ‘Pipe to a program’ radio button and, in the text field next to it, enter the path (relative to your home directory) of the processing script created from the above steps
  • Click ‘Add forwarder’ – your new forwarder should be created

Now any email sent to the address you specified will be pumped to the script just created. We’re then free to do any processing we want.

Gotchas and things to check when it doesn’t work

There’re a lot of things that can go wrong with this – some of the ones I encountered are below.

All script output is considered to be an error
Anything your script outputs is considered by the mailer to be a problem and will cause the email to bounce back to the sender. While it might seem like you’re not outputting anything, the following non-exhaustive list are some examples:

  • Anything you output using PHP’s echo method (or equivalent)
  • Any ‘die’ statements that are called
  • Any HTTP headers (hence our using the -q switch)
  • Any syntax errors output by the PHP binary when it’s fired up
  • Any stray whitespace before the opening <?php tag or after the closing ?> tag

That last one caused some significant issues for me. By any whitespace I mean any whitespace – and not just in the PHP file that’s the target of your pipe but in anything it subsequently includes through require_once or similar.

One way to test this is to run your script on the command-line. If it outputs anything at all you’ll see it in the console – perhaps as stray whitespace, perhaps as more obvious parser errors.

Failing to read all of STDIN may be interpreted as an error too
This one I wasn’t expecting but failing to read all of STDIN may also be interpreted as an error state. The easiest way to handle this is to simply read the whole damned thing.

Debugging

Given that pretty much anything that your script does wrong will cause a bounce, and that the bounce may or may not contain useful information, it’s useful to split out the bulk of your processing logic into a separate file and have your endpoint responsible only for parsing STDIN into a structured email and calling your actual processing logic. Then you can have a test harness around your actual processing logic (perhaps a simple webpage that lets you paste in an email) and test it offline, without having to try to diagnose issues solely from having emails bounce.

It can also be handy to store all incoming emails in a file or database table for a short period of time to help you diagnose issues in conjunction with your test harness. If you get a bounce during development, you can dig up the email that caused the problem and fire it into your test harness.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.