GreenArrow Email Software Documentation

Running Custom Code During SimpleMH Injection

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 the X-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;
	}
};


Copyright © 2012–2024 GreenArrow Email