Illustration showing email symbols moving out from a light source

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/config or /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:

  driver = accept
  domains =
  transport = example_transport

This is a basic router which matches any email sent to 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 /etc/exim4/conf.d/transports.

Here’s a transport that invokes a PHP script:

  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 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:
// 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:

  environment = foo=bar,demo=example

Return Values

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.

Command Invocation

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.

  timeout = 5m

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 pipe driver.

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.

Profile Photo for James Walker James Walker
James Walker is a contributor to How-To Geek DevOps. He is the founder of Heron Web, a UK-based digital agency providing bespoke software development services to SMEs. He has experience managing complete end-to-end web development workflows, using technologies including Linux, GitLab, Docker, and Kubernetes.
Read Full Bio »