Part 1 introduced SparkPost Signals for on-premises deployments. In this part, let’s dive into the details of setting up PowerMTA for SparkPost Signals. You’re going to need:

  • A host to run the latest version of PowerMTA on – either new, or an existing machine
  • A SparkPost account with API key permission for “Incoming Events: Write” as described

We’ll set up PowerMTA to stream events up to your SparkPost account, then you’ll be able to use the following:

Signals reporting such as Health Score and Engagement Recency will take more setup surrounding PowerMTA for engagement tracking, covered in a coming article.

Firstly, install (or upgrade) to PowerMTA 5.0, following the usual instructions which are pretty straightforward. Then we’ll work through the following steps:

  • Configure PowerMTA connector to SparkPost Signals
  • Select which PowerMTA traffic streams to report to Signals
  • How to use meaningful names that show up well in reporting.

We’ll also cover the other specific PowerPMTA setup aspects used in our Signals demo:

  • FBL events (Spam Complaints) and remote (out-of-band) bounces
  • Injection configuration, including DKIM
  • FBL and OOB configuration
  • VirtualMTA setup and naming (and how this appears in your SparkPost Signals reports)

Finally, there’s a “bonus feature” with code to ensure your campaign names are compatible with PowerMTA X-Job  name conventions.

Configure PowerMTA connector

The Signals configuration is described in the User Guide section 10.1. Here we’ll start with “Use Case #2”, which enables Signals for all traffic from this PowerMTA host.

Here’s what each attribute does:


This is unique to your SparkPost account, it’s the value you got from SparkPost earlier.


This needs to match the address of your SparkPost API service, whether it’s US or EU. For more info see here. The usual values are:

SparkPost (US):

SparkPost EU:


This directive is optional and when enabled, gives a bit more info in the pmta.log file, which can be useful during setup to confirm that everything’s working correctly. Each minute, even when there’s no traffic, you’ll see:

2019-07-26 11:47:57 Signals: Discovered 0 files

With traffic, you’ll see something like:


With this flag set, PowerMTA tells SparkPost Signals to look for engagement events (opens and clicks) relating to email deliveries. The open and click tracking requires an external integration, covered in part 4 of this series, rather than coming from PowerMTA itself.


This tells PowerMTA the disk space threshold at which it should start to delete the oldest SparkPost JSON event files to make space for new files when disk space is running low.


This tells PowerMTA to upload to Signals, in this case globally for all traffic (more info here). You can be more selective about what traffic streams to upload if you wish.

You can also mark particular PowerMTA traffic to be reported as belonging to a SparkPost subaccount – this is another way to distinguish one particular traffic stream from another.

Select which PowerMTA traffic streams to report to Signals

You can select Signals to be active:

  • Globally (this is what we used in the above example)
  • For some Virtual MTAs and not others
  • For some Virtual MTA pools and not others
  • For specific “Sender” or “From” addresses relayed by PowerMTA, in combination with the Virtual MTA / Virtual MTA pool selections

This configuration is very powerful and is illustrated through a series of example use-cases in the User Guide.


Here’s a view of SparkPost Signals, connected to PowerMTA. You can see on July 19, about half-way through this sequence, that engagement tracking (described in part 4) is switched on and begins to drive the health score. (Until you have that, the health score will just show values around 50).

The Campaign names are available as reporting facets, along with Subaccount, IP Pool, Mailbox Provider, and Sending Domain.

Using meaningful names that show up well in reporting

Setting up the PowerMTA VirtualMTA Pool names and Job names to be meaningful and human-readable is well worth doing. These show up directly in your SparkPost Signals facets and the Summary report.

As mentioned earlier, you don’t need to create these pools in your SparkPost account. SparkPost picks them up from your PowerMTA configuration.

Here’s how PowerMTA configuration terms translate to SparkPost terms.

PowerMTA term SparkPost Reports / Signals term
Recipient Domain
(domain portion of “rcpt” field in Accounting file).
Recipient Domain
The domain portion of the “Sender” or “From” header in the message relayed by PowerMTA.
(domain portion of “orig” in Accounting file).
Sending Domain
VirtualMTA (name)
VirtualMTA Pool (name)
(“vmtaPool” in accounting file)
IP Pool (name)
smtp-source-host a.b.c.d
(“dlvSourceIp” in accounting file)
Sending IP a.b.c.d
Job (name)
(“jobId” in accounting file)
Campaign ID (name)
Template (name)
“Subaccount” is not a native PowerMTA concept.

However, PowerMTA can tag virtualMTAs, virtual MTA Pools, or Sender-or-From domains with a subaccount ID for SparkPost reporting purposes.

Subaccount ID (number)
FBL (event) Spam Complaint (event)
Remote Bounce (event) Out-of-Band bounce (event)


Setting up at least one smtp-source-host  address also enables SparkPost to correctly identify the sending IP address so that it shows up on Injection and Delivery events, as well as in the Summary report view.

Job names are set in PowerMTA via a header in the injected message. As well as enabling individual job control (pause/resume etc) which is useful in itself, PowerMTA passes the names through to SparkPost Signals reporting as “campaign ID”. See User Guide section 12.8 “Tracking a campaign in PowerMTA with a JobID”.

There are a few things to be aware of regarding job naming. While SparkPost (with JSON format, and JSON escaping) allows characters such as <SPACE>  in campaign names, mail headers are more restrictive. Valid characters allowed in the X-Job  header are:

A-Za-z0-9!#$%&'()*+,-./:;<=>[email protected][\]^_{|}~ 

In other words, disallowed characters include <SPACE>, double-quotes   and backtick `. If you’re used to working with X-Job names, this won’t be surprising, and your campaign ID names will “just work” on SparkPost reporting. If like me, you learned SparkPost first, you might want a tool to ensure your X-Job values are safe; see the bonus feature at the end of this article.

FBL events (Spam Complaints) and remote (out-of-band) bounces

PowerMTA can receive and process FBL events (known in SparkPost as Spam Complaint events) and remote bounces (known in SparkPost as out-of-band bounces, because the reply comes back some time afterward, rather than during the SMTP conversation).

There are articles in the Port25 Support Forum on how to set up the Bounce Processor and the FBL Processor. If you are an existing PowerMTA user, you probably already have these.

Here’s the configuration I made for a demo, based on these articles and oriented towards hosting PowerMTA in Amazon EC2.

Injection configuration

We’ll use port 587 for injected messages, which will come over the public Internet from another host. We need to stop bad actors discovering and abusing this service, so we apply username/password authentication and optional TLS, similar to SparkPost SMTP injection endpoints.

We want to be able to send messages from sources that are properly authenticated to any destination. We also want a separate listener on port 25 for FBL and remote bounce responses that don’t require authentication

In the following <source>  declarations, we’re using username/password authentication and optional TLS to defend against rogue message injection. We also set rate limits on connections making failed password attempts.

Your setup might be different; for example, if you have a private network between injector and PowerMTA, you won’t need password authentication.

The <source {auth}>  declaration (see here) applies once authentication has passed. Here, it allows onward relaying, sets up the default virtual MTA group to use, and adds the X-Job header (which will be reported by SparkPost Signals as campaign_id).

The rewrite-list maps injected messages to use a specific MAIL FROM domain (aka bounce domain or Return-Path:).

Then we set up our TLS configuration and SMTP username / password.

We can check that the (insecure, deprecated) TLS v1.0 is not accepted using my favorite SMTP test tool,  swaks.

We see:

Let’s also apply DKIM signing on our outgoing messages, as it’s good practice (I followed these instructions to set up the key).

FBL and OOB configuration

Now .. finally .. we declare which specific domains are open for remote bounce and FBL responses. We don’t want to relay those anywhere (to prevent backscatter attacks), just internally process those responses.

You can see I set up two bounce domains, as I was playing around with using/not using the mfrom  rewrite rule.

The FBL domain is usually then registered with external services such as Microsoft SNDS; see this article for more information. For this demo, the FBLs will be coming from the Bouncy Sink, so no need to register.

Testing the SMTP listener

It’s important to test that your SMTP listener is requiring authorization for any general destinations, rejecting any messages that are not specifically addressed to the FBL and remote-bounce domains.

The response, as expected, shows that relaying is denied:

VirtualMTA setup and naming

PowerMTA VirtualMTAs (and VirtualMTA pools) are powerful features for managing message streams, and PowerMTA / SparkPost Signals reporting features work best with these active.

The virtual-mta-pool  setting is reported in SparkPost as “IP Pool”, and is available as a SparkPost Signals reporting facet (the drop-down menu underneath the charts).

The Summary Report also shows IP Pool as a “Group By” reporting facet.

As noted earlier in this article, setting up at least one  smtp-source-host address also enables SparkPost to correctly identify the sending IP address, so that it shows up on Injection and Delivery events, and on the Summary Report:

That’s all you need to get a basic integration working between PowerMTA and SparkPost Signals. You’ll find the full config file example here.

In Part 4 of this series, we set up Engagement Tracking alongside PowerMTA and stream the events into SparkPost. That will enable the Health Score and Engagement Recency charts to provide useful information, as well as making open and click events available on the Events Search and Engagement reports.

Before you go, here’s the bonus feature I mentioned.

Bonus feature: X-Job name checking/filtering

To ensure any character string is safe for use as a PowerMTA X-Job  name, here’s a simple Python function to map any unsafe characters to an underscore “_”

This uses Python regular expressions in a specific way. It declares the set of disallowed characters using the “set complement” operator ^ rather than list all allowed chars. That means we catch (and make safe) characters beyond the usual 7-bit set. We can show that using this test fragment:


You can see that <SPACE>, double-quotes , and backtick `, as well as all characters beyond ~ are mapped to underscore.

~ Steve