Skip to main content

Deploying Signals for On-Premises: PowerMTA Integration. Learn how to configure PowerMTA for seamless integration with SparkPost Signals.

Deploying Signals for On-Premises: PowerMTA Integration

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 ... successfully and 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) or pmta.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-space directive 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 PowerMTA X-Job header format, replacing unsafe characters with underscores.

Installation and configuration overview

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 PowerMTA 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.

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.

# Enable Bounce and FBL processing on specific domains

relay-domain pmta.signalsdemo.trymsys.net relay-domain bounces.pmta.signalsdemo.trymsys.net relay-domain fbl.pmta.signalsdemo.trymsys.net

<bounce-processor> deliver-unmatched-email no deliver-matched-email no <address-list> domain pmta.signalsdemo.trymsys.net domain bounces.pmta.signalsdemo.trymsys.net </address-list> </bounce-processor> <feedback-loop-processor> deliver-unmatched-email no deliver-matched-email no <address-list> domain fbl.pmta.signalsdemo.trymsys.net </address-list> </feedback-loop-processor>

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.

# SparkPost Signals

<signals> api-key ##my ingest API key here## upload-url https://api.sparkpost.com/api/v1/ingest/events log-verbose true min-free-space 1G engagement-tracking sparkpost customer-id 123 </signals>

enable-signals true

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

Scope

What gets reported to Signals

When to use it

Global

All traffic from the PowerMTA host

Simple deployments where all traffic should feed into SparkPost Signals.

VirtualMTA

Traffic from selected VirtualMTAs only

When you want separate reporting views for different IPs or traffic types.

VirtualMTA Pool

Traffic from selected VirtualMTA pools

When you group IPs into pools and want pool-level reporting.

Sender / From domain

Messages from specific sender or From domains

When you need per-client or per-brand reporting within the same infrastructure.

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

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.

Health score dashboard.

Signals integration screen.

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 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)

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.

IP address(es) and port(s) on which to listen for incoming SMTP connections

smtp-listener 0.0.0.0:587 smtp-listener 0.0.0.0:25

--

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.

# Route all outgoing traffic through this virtual mta / pool.

Declare the delivery IP address here, so that SparkPost signals ingest injection (aka "reception") events

will carry the correct sending_IP attribute

<virtual-mta mta1> smtp-source-host 172.31.25.101 pmta.signalsdemo.trymsys.net </virtual-mta> <virtual-mta-pool default> virtual-mta mta1 <domain *> max-smtp-out 20 bounce-after 4d12h retry-after 10m dkim-sign yes </domain> </virtual-mta-pool>

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 "_"

import re

def pmtaSafeJobID(s):
    """
    :param s: str
    :return: str
    Map an arbitrary campaign ID string into allowed chars for PMTA X-Job header.
    See https://download.port25.com/files/UsersGuide-5.0.html#tracking-a-campaign-in-powermta-with-a-jobid
    Specifically disallow <sp> " ` but allow through most other chars.
    """
    disallowedChars = '[^A-Za-z0-9!#$%&\'()*+,\-./:;<=>?@$\\\\$^_{|}~]'
    return re.sub(disallowedChars, '_', s)