Exim is a popular message transfer agent (MTA) for Unix systems. It offers a wide variety of routing and transportation options. In this article, we’ll show how to use Exim to pipe incoming emails into your own script.
We’ll assume you’ve already got a functioning Exim server that’s able to receive email. The official wiki provides informative guidance if you’re starting from scratch with a fresh installation.
Managing Exim Configuration
Available configuration approaches vary by operating system distribution. An Exim installation built from source will use
src/configure.default as its config file.
The file for Exim installed from a package manager is typically
/etc/exim.conf. You can find the path that’s currently used by running
exim -bP configure_file.
Debian-based operating systems have a slightly more complicated system. There are two possible configuration methods: a single file,
/etc/exim4/exim4.conf.template, or split config files within
/etc/exim4/conf.d. You need to run
update-exim4.conf after you make changes. This creates a single merged file that’s actually read by Exim.
Exim should be restarted each time you change its configuration. Run
service exim4 restart to apply your changes.
Creating a Router
The first step in getting email to your application is defining a custom router. Routers match incoming emails against a set of conditions to determine the delivery mechanism to use.
Routers are processed in the sequential order they’re found in the configuration file. Any routers above yours in the file could match incoming emails first.
Add your router to your configuration file within the
ROUTERS CONFIGURATION section. If you’re using the split Debian configuration, you can create a new file in
/etc/exim4/conf.d/routers instead. Routers will be combined in alphabetical order when you use this approach.
Here’s an example router:
example_router: driver = accept domains = example.com transport = example_transport
This is a basic router which matches any email sent to
example.com. When a match is processed, Exim will
accept the message and deliver it using the
example_transport transport. We’ll create this next.
Creating a Transport
Once an email has been accepted by a router, it’s delivered by a transport. Transports are responsible for implementing the message delivery routine. Different drivers can send over the internet using SMTP, transfer to a local Unix user, or write to a file.
Creating a custom transport based on a built-in driver lets you deliver matching emails to your own application. You can then deal with each email individually, outside of Exim.
Unlike routers the order of transports does not matter. Each incoming email will match to the single transport specified by the router that accepts it. Add your transport anywhere in the
TRANSPORTS CONFIGURATION section of your Exim file. Debian users with split configuration enabled can create a file in
Here’s a transport that invokes a PHP script:
example_transport: driver = pipe command = /usr/bin/php /var/www/html/handle_incoming_email.php user = www-data group = www-data
When combined with the example router from above, any email sent to
@example.com will be delivered to
handle_incoming_email.php. Exim supplies the entire email in RFC-compliant format as the command’s standard input for you to read in your programming language.
// Get email content from standard input in PHP $email = fopen("php://stdin", "r"); // To: email@example.com // Subject: Demo Email // // This is an email.
The transport is configured with the
pipe driver. This uses a Unix pipe to supply the incoming email to your
command. The command will be executed as the user and group you specify.
Using Environment Variables
Exim will set several environment variables before executing your command. These can be used to access information about the message without having to parse the full email format.
Available variables include:
DOMAIN– The domain the incoming email was sent to.
MESSAGE_ID– Exim’s internal ID representing the email.
RECIPIENT– The email address which the message was sent to.
SENDER– The email address which the message originated from.
You can add additional variables to the command’s environment by setting the
environment option in your transport. This accepts a comma-separated list of key-value pairs:
example_transport: environment = foo=bar,demo=example
Your command should exit with a
0 status code to confirm a successful delivery. A non-zero exit code will be interpreted as a failed delivery. The sender will receive a bounce message.
This behavior is disabled by setting the
ignore_status option in your transport. Exim will then treat non-zero status codes as successful.
In addition, you can use the
temp_errors option to define script exit codes which indicate an error was encountered but is expected to be temporary. When Exim sees one of these status codes, message delivery will be deferred and retried later on.
The content of bounce messages can be customized with the
return_fail_output option. When this is set to
true, Exim will include the contents of your script’s standard output in its bounce email. The mechanism facilitates use of your application code to deliver personalized bounce responses to senders.
Exim usually invokes your command directly from the transport. If you set the
use_shell option, it will pass the command to
/bin/sh instead. This can be useful in scenarios where your script requires a full shell environment to be available.
Command execution times out after one hour with the default configuration. A timeout is treated as a delivery error. The command will be terminated and a bounce message returned to the sender. The timeout can be customized with the
timeout setting. Additionally setting the
timeout_defer option will cause timeouts to be treated as temporary errors, allowing another delivery attempt after a delay.
example_transport: timeout = 5m timeout_defer
You should also consider that your script might be executed concurrently if multiple emails arrive at the same time. Your application should be able to withstand this possibility. Locking systems that allow only one instance to run simultaneously should be disabled for your email handling script.
Exim is easily configured to route incoming email to your own application. A simple setup that directs all received messages can be achieved with a basic router coupled to a transport using the
You need to provide a binary that accepts an email on standard input as part of your application. The binary will be invoked by Exim whenever a new message is received. You should then parse the email in a standards-compliant manner to pull out the body content and message headers relevant to your system.