Deploying Signals for On-Premises: PowerMTA Integration
Bird
30 Aug 2019
1 min read

Key Takeaways
Purpose: This guide explains how to integrate PowerMTA 5.0+ with SparkPost Signals to stream event and engagement data (bounces, opens, clicks, spam complaints) from on-premises MTAs directly into the SparkPost analytics layer.
Core configuration:
Add enable-signals true and define your SparkPost ingest endpoint (https://api.sparkpost.com/api/v1/ingest/events or the EU equivalent).
Use a valid API key with “Incoming Events: Write” permission.
Specify customer-id, and optionally set up custom tracking domains for improved deliverability.
Tracking setup: PowerMTA’s Engagement Tracking automatically injects open and click pixels into HTML emails. You can disable tracking per link with the data-msys-clicktrack="0" attribute.
Selective reporting: Signals can be enabled globally or limited to certain VirtualMTAs, pools, or sender domains, allowing fine-grained data control.
Testing & verification: Use the Signals Integration dashboard and PowerMTA logs to confirm event ingestion and track Health Scores, bounces, and engagement metrics in real time.
Deliverability tuning:
Use meaningful VirtualMTA and Job names — these map directly to IP Pools and Campaign IDs in SparkPost reports.
Configure DKIM signing, TLS enforcement, and proper relay rules to prevent unauthorized injections.
Advanced setup: The article also includes ready-to-use snippets for FBL & out-of-band bounce handling, authenticated SMTP injection (port 587), and Python code to sanitize X-Job headers for compatibility.
Q&A Highlights
What does Signals integration actually do?
It automatically uploads PowerMTA’s message events (injection, delivery, bounce, engagement) into your SparkPost account so you can access dashboards like Health Score, Delay reports, and Spam Trap Monitoring.
Why integrate Signals with an on-prem MTA?
Many enterprises run self-hosted mail infrastructure for compliance reasons but still want SparkPost’s analytics and monitoring capabilities. Signals bridges that gap without migrating mail delivery to the cloud.
How can I verify that events are flowing to SparkPost?
Check the PowerMTA logs for
Signals: Transferred ... successfullyand confirm event entries under Signals → Events Search in SparkPost.Can I use my own tracking domain?
Yes — configure a CNAME such as
track.mycompany.com → pmta.spgo.io(US) orpmta.eu.spgo.io(EU), then register and verify it in SparkPost for branding and reputation consistency.What about data privacy or disk usage?
The
min-free-spacedirective automatically deletes old JSON event files when disk space runs low, preventing local buildup of telemetry data.What’s the “bonus feature” at the end?
A Python regex utility (
pmtaSafeJobID) that ensures campaign/job names use only characters valid in the PowerMTAX-Jobheader format, replacing unsafe characters with underscores.
Let’s dive into the details of setting up PowerMTA for SparkPost Signals.
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 here
We’ll set up PowerMTA to stream events up to your SparkPost account, then you’ll be able to use the following:
Firstly, install (or upgrade) to PowerMTA 5.0 r4 or later, following the usual v5.0 install instructions which are pretty straightforward. Then we’ll work through the following steps:
Configure PowerMTA connector to SparkPost Signals
Set up Engagement Tracking with a custom tracking domain
Select which PowerMTA traffic streams to report to Signals
Testing that your events are reaching Signals
Review 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 5.0 User Guide section 10.1. Here we’ll start with “Use Case #2”, which enables Signals for all traffic from this PowerMTA host, and enable SparkPost engagement tracking.
Here’s what each attribute does:
api-key
This is unique to your SparkPost account, it’s the value you got from SparkPost earlier.
upload-url
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): https://api.sparkpost.com/api/v1/ingest/events
SparkPost EU: https://api.eu.sparkpost.com/api/v1/ingest/events
log-verbose
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:
min-free-space
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.
enable-signals
This tells PowerMTA to upload to Signals, in this case globally for all traffic (more info here, for v5.0). 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.
engagement-tracking, customer-id
PowerMTA’s Engagement Tracking solution defaults to the tracking domain for the SparkPost US-hosted service. You specify your SparkPost numeric customer ID; here’s instructions for finding it.
tracking-domain
For SparkPost EU accounts, add the following line:
tracking-domain pmta.eu.spgo.io # this is the endpoint for SparkPost EU
Custom Tracking domain
If you would prefer to use your own tracking domain (this is better from a deliverability standpoint), do the following:
Create tracking domain with your DNS provider by creating a CNAME record. This will usually be a subdomain of your top-level domain, e.g. track.mycompany.com .
You can also use HTTPS tracking domains, although this is more involved (see the SparkPost configuration steps for HTTPS tracking domains).
Register tracking domain in your SparkPost account, and verify it. Wait a few minutes before trying this, to allow your DNS changes to propagate through the Internet, depending on your DNS provider.
Configure PowerMTA to use that domain instead of the default, with
tracking-domain yourdomain.com # Put your own domain here
You can check that your delivered emails have “open pixels” added and the links wrapped, by looking at the internals of the mail (in Gmail, use the menu top-right and choose “Show Original”).

You’ll notice the open pixels at the beginning and end of the HTML in the email. Each HTML link is also changed to have REF pointing to the tracking domain.

That’s all you need to get SparkPost Signals working with PowerMTA’s built-in Engagement Tracking.
Preventing specific links in your html email from being tracked
You can prevent PowerMTA from tracking specific links, by setting the custom attribute data-msys-clicktrack to “0” :
<a href="#" data-msys-clicktrack="0">Example</a>
PowerMTA will not wrap the link. It will also remove that attribute prior to transmitting the message to your recipient.
Select which PowerMTA traffic streams to report to Signals
Testing that your events are reaching Signals
Here’s a view of SparkPost Signals, connected to PowerMTA. You can see that the health score is varying.

The Campaign names are available as reporting facets, along with Subaccount, IP Pool, Mailbox Provider, and Sending Domain.
As well as looking at the PowerMTA logs, you can check that events data is reaching SparkPost by looking at the Signals Integration screen.

In your SparkPost Events Search screen, you should see events appear within a few minutes. These will include Injection and Delivery events, as well as Bounce, and potentially Out-of-Band Bounce and Spam Complaint events, if you’ve already configured PowerMTA to handle those for you.
If you have Engagement Tracking enabled, you will also see open , initial_open , and click events.
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 termSparkPost Reports / Signals termRecipient Domain
However, PowerMTA can tag virtualMTAs, virtual MTA Pools, or Sender-or-From domains with a subaccount ID for SparkPost reporting purposes.
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 v5.0 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!#$%&'()*+,-./:;<=>?@[\]^_{|}~
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.
If you’re familiar with PowerMTA configuration in this area, you can skip this part, down to the next horizontal line.
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. v5.0) 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, according to current recommendations.
We can check that the (insecure, deprecated) TLS v1.0 is not accepted using my favorite SMTP test tool, swaks.
We see:
Likewise for –tls-protocol tlsv1_1.
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:
550 5.7.1 relaying denied for recipient in "RCPT TO:<test@strange.pmta.signalsdemo.trymsys.net>
(End of demo setup description).
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.
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:
s='' for i in range(32, 256): s += chr(i) print(pmtaSafeJobID(s))
Giving
You can see that <SPACE>, double-quotes “, and backtick `, as well as all characters beyond ~ are mapped to underscore.



