GreenArrow Documentation

Running Custom Code During Delivery Attempts


The /var/hvmail/control/opt.pre_delivery_attempt_hook configuration file can be used to specify custom Perl code that should be run during every remote delivery attempt. This code can adjust delivery attempt parameters, such as which VirtualMTA to use.

Remote delivery attempts are delivery attempts to non-local domains which will are done through a network protocol like SMTP.

No services need to be restarted to apply changes made to this file. They’re automatically detected within 1 second of saving changes.

Future versions of GreenArrow may stop supporting Perl and support a different language more typically used for embedding hooks such as this – for example Lua or JavaScript.

The /var/hvmail/control/opt.pre_delivery_attempt_hook file must evaluate to valid Perl code in a use strict context and must return a reference to an anonymous subroutine.

The anonymous subroutine must return very quickly, as this is called in a single-threaded manner for all delivery attempts. Do not contact any external resources. Use of this feature may reduce the maximum delivery speed of GreenArrow.

Messages can be logged to the /var/hvmail/log/rspawn-limiter multilog directory by printing to STDERR.


The anonymous subroutine will be called with the following arguments:

(1) A hash containing information about the delivery attempt that is about to be made. The following keys will exist:

Key Meaning
mtaid VirtualMTA of the message. If you’re using a Routing Rule, then note that this is the name of the routing rule itself - not a VirtualMTA that the Routing Rule references.
sendid SendID of the message
listid ListID of the message
msguid unique identifier of the message
injtime UNIX seconds past the epoch that the message was injected
sender Envelope from address (Return-Path)
recipient Envelope recipient address
is_first_attempt 1 if the delivery attempt is happening from the ram-queue and 0 otherwise. A value of 1 usually means that it’s the first delivery attempt. Exceptions include deliveries to local domains where the first delivery attempt was deferred or throttled, and bounces. See the Queues section of GreenArrow Concepts for details on when the ram-queue is used.

(2) A reference to a hash that may be used for storing state.

This will be a reference to the same hash for every invocation of this subroutine, so any data added will exist for the next call to this subroutine.

However, when this subroutine is automatically re-loaded and re-compiled (due to the /var/hvmail/control/opt.pre_delivery_attempt_hook file being changed), then the old state hash will be discarded and a new state hash will be created.

This state is not preserved when the software is restarted.


The subroutine must return a reference to a hash.

The only required key is action, which defines what should be done.

action value Meaning
normal Don’t make any changes to the delivery attempt.
pause Simulate a deferral. You may optionally specify a deferral message by setting the message key.
dump Dump the message from the queue. This simulates a bounce. You may optionally specify a bounce message by setting the message key.
new_mtaid Change this delivery attempt to use a different VirtualMTA. The name or id of the new VirtualMTA should be provided using the new_mtaid key.

Here are example outputs for each of the above actions:

{ action => "normal" }

{ action => "pause", message => "Not going to deliver this message right now." }

{ action => "dump", message => "This message blocked by policy." }

{ action => "new_mtaid", new_mtaid => "priority_ips" }

Keys that apply to multiple actions:

  • deliver_as_if_to_domain – For the action values of normal and new_mtaid, this provides the domain name that will be used when applying Routing Rules and throttling in IP Addresses. By default the domain name of the recipient email address is used. (This feature may be removed in future versons of the software.)

Error Handling

All errors are logged to the /var/hvmail/log/rspawn-limiter/ multilog directory.

To see new entries to this logfile run this command:

tail -F /var/hvmail/log/rspawn-limiter/current | tai64nlocal

If executing the anonymous subroutine causes an exception or returns an invalid return hash, then the delivery attempt will result in a temporary failure with the status message Error in pre_delivery_attempt_hook (see logfile), and the error will be logged in the above-mentioned logfile.

For example:

error in pre_delivery_attempt_hook: msguid=[1483391740.41125123], recipient=[[email protected]], error: Exception running pre_delivery_attempt_hook: Illegal division by zero at (eval 10) line 2.

If the subroutine configuration file fails to compile or does not return a reference to a subroutine, then the problem will be logged to the above-mentioned logfile once-per-second, and all delivery attempts will be temp failed with the status message Error in pre_delivery_attempt_hook (see logfile).

This is an example of what would appear in the log:

error loading pre_delivery_attempt_hook: error compiling: syntax error at (eval 12) line 3, near "foo"


Email Address Blacklist

This code creates a blacklist where delivery attempts to email addresses listed in the file /tmp/recipient_blacklist.txt result in a permanent failure with the status message This address is on an internal blacklist. (#5.7.1). The blacklist is re-loaded every 3 seconds.

return sub
	my $input = shift;
	my $state = shift;

	## Load configuration

	if ( ! exists($state->{last_read_time}) || abs( $state->{last_read_time} - time() ) > 3 )

		open(PRE_DELIVERY_HOOK_CONFIG, "<", "/tmp/recipient_blacklist.txt") or die("error opening file: [email protected]");

		$state->{recipient_blacklist} = {
			map { (lc($_),1) }
			map { s/^\s+//; s/\s+$//; $_ }


		$state->{last_read_time} = time();

	## Apply blacklist

	my $recipient = $input->{recipient};

	if ( exists $state->{recipient_blacklist}{lc($recipient)} )
		return { action => 'dump', message => 'This address is on an internal blacklist. (#5.7.1)' };

	return { action => 'normal' };


Change VirtualMTA

This code changes the VirtualMTA to smtp1-3 for any email with the SendID of trans170101.

return sub
	my $input = shift;
	my $state = shift;

	## Change the VirtualMTA of some messages after-the-fact

	if ( $input->{sendid} eq "trans170101" )
		print STDERR "pre_delivery_attempt_hook: matched!\n";
		return { action => 'new_mtaid', new_mtaid => 'smtp1-3' };

	return { action => 'normal' };


Do Nothing

This is the simplest code that does not change any of the delivery attempts.

return sub
	return { action => 'normal' };