Running Custom Code During Delivery Attempts
- Table of Contents
- Overview
- Input
- Output
- Error Handling
- Examples
Overview
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
.
Input
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. |
mtaid_exists |
1 if the VirtualMTA represented by mtaid was found and 0 otherwise. |
(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.
Output
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. |
new_config |
Change some delivery attributes. Return value may include the fields new_mtaid or new_sender to replace those delivery attributes. |
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" }
{ action => "new_config", new_mtaid => "priority_ips", new_sender => "[email protected]" }
Keys that apply to multiple actions:
-
deliver_as_if_to_domain
– For the action values ofnormal
andnew_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"
Examples
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: $@");
$state->{recipient_blacklist} = {
map { (lc($_),1) }
map { s/^\s+//; s/\s+$//; $_ }
<PRE_DELIVERY_HOOK_CONFIG>
};
close(PRE_DELIVERY_HOOK_CONFIG);
$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' };
}