Testing a custom exim4.conf file for syntactical errors

Often you will come to a scenario when you want to hack the exim configurations and you end up generating a new exim4.conf file. The beauty of exim is that, you can even copy this file from another instance and make it run perfectly in your instance.
* To make sure that your exim4.conf file contains no syntactical and various other errors :

root@box:~# sudo exim -C </path/to/new/exim4.conf> 

For a syntactically perfect exim4.conf, you will receive a welcome message like:

root@box:~# exim -C /etc/exim4/exim4.conf
Exim is a Mail Transfer Agent. It is normally called by Mail User Agents,
not directly from a shell command line. Options and/or arguments control
what it does when called. For a list of options, see the Exim documentation.

and in the else case

root@box:/etc/exim4# exim -C exim4.conf
2014-09-20 03:36:10 Exim configuration error in line 6 of exim4.conf:
#error message here

Happy exim-hacking 🙂
Ref: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_run_time_configuration_file.htmle


Exim: Creating and using Macros

The topic looks easy, but implementing them was a great learning experience, as I found it. Macros helps to make reuse a lot of code, and make the exim configuration look tidy. In the earlier post, I scribbled how to define an Exim regex to capture all VERPed emails as :

begin routers

	driver = accept
	domains = +local_domains
	condition = ${if match{$local_part}{^bounces-\w+-\w+-\w+-\w+$}{true}{false}}
	transport = mwverpbounceprocessor

and under transports:

	driver = pipe
	command = /usr/bin/curl -H 'Host: <%= @verp_post_connect_server %>' <%=@verp_bounce_post_url
	%> -d "action=bouncehandler" --data-urlencode"email@-"
	user = nobody
	group = nogroup

Tidying this up a bit, we can make something like:

#Under main configurations 
VERP_BOUNCE_LOCALPART_REGEXP = ^bounces-\w+-\w+-\w+-\w+$
VERP_BOUNCE_POST_URL = http://localhost/api.php

	driver = accept
	domains = +local_domains
	condition = ${if match{$local_part}{VERP_BOUNCE_LOCALPART_REGEXP}{true}{false}}
        transport = mwverpbounceprocessor

and under transports

	driver = pipe
	command = /usr/bin/curl action=bouncehandler --data-urlencode email@- VERP_BOUNCE_POST_URL
	user = nobody
	group = nogroup

Hope it helps someone tidy-code 😀

Exim regex: Capture all VERPed emails with a given header pattern

Consider your VERP generater produces a Return-Path header of the form:
and you want a router to capture all bounce emails having this/similar as the To header. This router can serve multiple purpose like – feeding to a bounce processor, silently killing all bounces ( not intended ) or POSTing the email to an API.
Suppose you have a router named bounceprocessor that HTTP POST to an API named bouncehandler at localhost/api.php, we will design the Regex as:

begin routers

	driver = pipe
	domains = +local_domains
	condition = ${if match{$local_part}{^bounces-\w+-\w+-\w+-\w+$}{true}{false}}
	command = /usr/bin/curl "action=bouncehandler" --data-urlencode "email@-" http://localhost/api.php
	user = nobody
	group = nogroup

Please note that

\w = words 
\d = digits 

Thanks to Mark Bergsma and Jeff Green for helping me with the regex.

A simple test to detect a permanent bounce

There are n chances for an email to get bounced back after being rejected and discussing it all here is out of scope of this post, and are broadly categorized into permanent (hard) and temporary (soft) bounces. Jotting down some reasons for a temporary bounces:

  • A server is unavailable or down -Network failure
  • The server is overloaded
  • Recipient mailbox is over-quota
  • Too long e-mail message
  • Vacation response is on

The reasons for permanent failures are still many, and those who are desirous, can check here.
Only the permanent bounces needs to get parsed, and actions taken upon, while temporary needs to be neglected. Since there are another n number of cases for a bounce to occur, there are n varieties of bounce emails too. We were searching for how to filter only the permanent bounces out.
We hit with this exim4-doc here and found that, exim4 adds an X-Failed-Recipients: header to permanent bounces. Sticking to that, filtering out the permanent bounces is a little regex code.

  * Filter a given boucne for hard or soft
  * @param string $email
  * @return array $headers
protected static function checkForPermanentBounce( $email ) {
	$emailLines = explode( "\n", $email );
	foreach ( $emailLines as $emailLine ) {
		if ( preg_match( "/^X-Failed-Recipients: (.*)/", $emailLine, $failureMatch ) ) {
			$headers[ 'permanentFailure' ] = $failureMatch;
		if ( trim( $emailLine ) == "" ) {
			// Empty line denotes that the header part is finished

	return $headers;

Now, on the calling function, you can just check:

$permanentFailure = $emailHeaders[ 'permanentFailure'];
if ( $permanentFailure !== null ) {
       //code to do processing!

Yay! Thats it.
PS: We sticked to exim, as mchnery uses exim. There are still chances of a bounce from an internal server, and we are working on how to fix that too. Thanks