Running Custom Code During SimpleMH Injection
- Table of Contents
- Overview
- Dangers and Debugging
- $EARLY_MODIFY_MESSAGE_SUB
- $MODIFY_MESSAGE_SUB
Overview
The /var/hvmail/control/simplemh-config
configuration file can insert custom Perl code that runs each time a SimpleMH message is injected into GreenArrow. The code can adjust message parameters like the message’s body, headers, and Mail Class.
The code should be placed in a function named $EARLY_MODIFY_MESSAGE_SUB
or $MODIFY_MESSAGE_SUB
, depending on when you want it to run:
-
$EARLY_MODIFY_MESSAGE_SUB
runs before SimpleMH does most of its processing to the incoming message. -
$MODIFY_MESSAGE_SUB
runs when SimpleMH is almost finished processing the message. By the time this function runs, SimpleMH has already processed and removed theX-GreenArrow-*
headers, but has not yet done the following:- Click and open tracking
- Sending to a seed list
- Message archiving
If you want to read or set X-GreenArrow-*
headers, you should use $EARLY_MODIFY_MESSAGE_SUB
, but if you want your code to key off of decisions that SimpleMH made based on those headers, then you should use $MODIFY_MESSAGE_SUB
.
Dangers and Debugging
The /var/hvmail/control/simplemh-config
file is evaluated by Perl every time a SimpeMH message is injected. That means there sources of danger when making edits to this file, including:
-
Syntax errors cause SimpleMH to stop working. Any time you save a change, check for errors by running the following command and verifying that it produces a
syntax OK
message:$ /var/hvmail/libexec/perl -c /var/hvmail/control/simplemh-config /var/hvmail/control/simplemh-config syntax OK
-
Slow running code will have a significant impact on SimpleMH performance. Your code should execute quickly and not depend on any external resources.
-
Changes take effect for new incoming messages immediately, so don’t save changes to
/var/hvmail/control/simplemh-config
until you’re confident about them. You may want to copy the file to an alternate location, and edit it to avoid accidentally deploying an incomplete set of changes.
If your syntax is good, but your code still doesn’t produce the desired results, review SimpleMH’s logs for clues:
-
/var/hvmail/log/simplemh/current
- SimpleMH’s log for messages injected via SMTP, QMQP and local injection -
/var/hvmail/log/simplemh2/current
- SimpleMH’s log for messages injected via the HTTP submission API
$EARLY_MODIFY_MESSAGE_SUB
The $EARLY_MODIFY_MESSAGE_SUB
function is called with the following arguments:
-
$headers_ref
- a reference to a string containing the email’s headers -
$body_ref
- a reference to a string containing the email’s body -
$recipients
- a reference to an array containing the email’s recipients
The function may use the pass-by-reference nature of $headers_ref
and $body_ref
to modify the headers or body of the message. For example:
$$body_ref .= "\nThis addendum brought to you by the EARLY_MODIFY_MESSAGE_SUB";
X-GreenArrow-MailClass Example
If a SimpleMH message is injected using SMTP AUTH, SimpleMH temporarily inserts an X-GreenArrow-INTERNAL-smtp-auth-username
header that records the Base64-encoded username. The header is removed before final delivery but is accessible to $EARLY_MODIFY_MESSAGE_SUB
. Here’s an example function that uses X-GreenArrow-INTERNAL-smtp-auth-username
header’s value to assign SMTP AUTH usernames to Mail Classes:
$EARLY_MODIFY_MESSAGE_SUB = sub
{
my $headers_ref = shift;
my $body_ref = shift;
my $recipients = shift;
my %username_assignments = (
'[email protected]' => 'mc_test_a',
'[email protected]' => 'mc_test_b',
);
if ( $$header_ref =~ /^X-GreenArrow-INTERNAL-smtp-auth-username: *([^\r\n]+)$/im ) {
my $username = decode_base64($1);
if (exists($username_assignments{$username})) {
$$headers_ref .= "X-GreenArrow-MailClass: $username_assignments{$username}\n";
}
}
return undef;
};
The Set Mail Class with SMTP AUTH page shows an alternative option that translates SMTP AUTH usernames into Mail Classes and optionally, Instance IDs without any custom code.
Copy another header into X-GreenArrow-Click-Tracking-ID
This example will copy the value of the X-SMTPAPI
header and use it as the click tracking ID for this message.
$EARLY_MODIFY_MESSAGE_SUB = sub {
my $headers_ref = shift;
my $body_ref = shift;
my $recipients = shift;
my $headers = $$headers_ref;
if ( $headers =~ m{^X-smtpapi:[ \t]*((?:[^\n]+|\n[ \t]+)*)\n}im ) {
my $x_smtpapi = $1;
# Replace any characters not supported by ClickTrackingID with spaces.
$x_smtpapi =~ s{[^\x20-\x7e]}{ }g;
# Add X-GreenArrow-Click-Tracking-ID to the top of the headers.
$headers = "X-GreenArrow-Click-Tracking-ID: " . $x_smtpapi . "\n" . $headers;
$$headers_ref = $headers;
}
};
This has the limitation that any characters not supported by X-Click-Tracking-ID
(which supports ASCII bytes between 32 and 126) will be replaced with spaces.
Most notably, if the X-SMTPAPI
header is wrapped, the newline in the wrapped header will be converted to a space.
Set mail class based on From domain
This example sets your X-GreenArrow-MailClass
header based on the lowercased
domain in the first address listed in the From
header. It uses a variable
$mailclass_mapping
to define the overrides.
$EARLY_MODIFY_MESSAGE_SUB = sub {
my $headers_ref = shift;
my $body_ref = shift;
my $recipients = shift;
my $headers = $$headers_ref;
my $mailclass;
my $mailclass_mapping = {
"discardallmail.drh.net" => "dev_null",
"nolimit.discardallmail.drh.net" => "dev_null",
};
if ( $headers =~ m/^[Ff][Rr][Oo][Mm]:[ \t]*((?:[^\n]+|\n[ \t]+)*)\n/m ) {
# Parse the From header.
my @addresses = Email::Address->parse($1);
if ( @addresses && $addresses[0]->address =~ m{([^@]+)\z} ) {
$mailclass = $mailclass_mapping->{lc($1)};
}
}
if ( defined $mailclass ) {
# Add X-GreenArrow-MailClass to the bottom of the headers -- overriding any other X-GreenArrow-MailClass header.
$headers .= "X-GreenArrow-MailClass: " . $mailclass . "\n";
$$headers_ref = $headers;
}
};
$MODIFY_MESSAGE_SUB
The $MODIFY_MESSAGE_SUB
function’s input is a hash with the following keys:
-
mailclass
- a string containing the name of the Mail Class that the message uses -
message
- a string containing the raw email, including its headers and body. The headers and body are normally separated by two Unix newline characters (\n\n
)
The function should return one of the following:
- A hash with a single key named
message
containing a modified message -
undef
if the message should be left unmodified
From Address Header Example
The following checks messages in the MailClass named mc_test_a
for a From
header, and if it’s missing, adds it in:
$MODIFY_MESSAGE_SUB = sub {
my $info = shift;
my $mailclass = $info->{mailclass};
if ( $mailclass eq "mc_test_a" ) {
my ($headers, $body) = split("\n\n", $info->{message}, 2);
$headers .= "\n";
my $headers_add = '';
if ( $headers !~ m{^From:\s}mi ) {
$headers_add .= "From: mc_test_a\@example.com\n";
}
return {
message => $headers_add . $headers . "\n" . $body,
};
} else {
return undef;
}
};