Knowledge base

Submission over HTTPS on its own listener, modeled on SparkPost

Transmissions API
An entry point with everything of its own — port, keys and source

A message is submitted with an HTTP request, and one request can carry either one recipient or several. The message body can be assembled with substitutions in the mail merge style.

The entry point is assembled from three parts. The <transmissions-api-listener> listener — necessarily with a certificate, because from outside this is HTTPS. The <transmissions-api> block — where the api-key keys and logging settings live. And a source block with the reserved name {transmissions-api}, through which the usual submission settings apply to such mail: permissions for service headers, stream selection and everything else.

Persistent connections are supported per RFC 7230. But with a caveat that catches clients out: every request on such a connection must carry Content-Length, otherwise the response is 400 bad request. The engine does not understand the keep-alive header with its parameters.

The request format is deliberately close to the SparkPost interface — to the point that unsupported fields of that interface are named as the reason for refusal. A client written for SparkPost most often works, and where it does not, it gets a clear refusal.

The main trap is a forgotten Content-Length on a persistent connection. The refusal comes not for the message but for the request — 400 bad request — and a client that worked perfectly without persistent connections starts failing once they are enabled. The cause is the header, not the content.

The other half is compression. A compressed request body is accepted, and the manual immediately advises limiting the maximum message size: without a limit, a compressed request becomes a way to exhaust the node’s memory by decompression.

What the Hub does. It does not use it: there is neither an entry point block nor a listener in its configuration — submission goes over ordinary SMTP with a login.

The Hub does have its own HTTP entry point on the node, but a different one: the web monitor and the management API with the http-api-key key. It is about managing the node, not submitting mail, and the two should not be confused.

Example configuration
# three parts of the entry point: a listener with a certificate, keys and source settings
<transmissions-api-listener 1.2.3.4:8585>
    tls-certificate /etc/pmta/transmissions-api-certificate.pem</transmissions-api-listener>

<transmissions-api>
    api-key 14ac5499cfdd2bb2859e4476d2e5b1d2bad079bf
    max-message-size 100M
</transmissions-api>

<source {transmissions-api}>
    process-x-virtual-mta yes
    process-x-job yes
</source>
The source name here is reserved — the engine uses it for mail that came in through this entry point. Without such a block the usual submission settings will not apply to it.
yellow — — directive names, blue — blocks. They are highlighted the same way in the text of the card.
▲

Common mistakes and problems

what people get wrong and how it ends3 mistakes▶

Enabling persistent connections without Content-Length

§ 8.1

What it costsEvery request on such a connection must carry the body length, otherwise the response is 400 bad request. A client that worked without persistent connections starts failing right after they are enabled.
How to spot it400 responses to requests that used to go through.
How to do it rightAdd Content-Length to every request or disable persistent connections on the client.

Setting up the entry point without a source block

§ 8.1.1

What it costsThe usual submission settings do not apply to this entry point’s mail: service headers are not read, stream selection does not work. Messages go out with defaults, and it is hard to understand why — there is not a single line about it in the configuration.
How to spot itHeaders that work for SMTP submission have no effect for HTTP submission.
How to do it rightDeclare a source block with the reserved name and list the needed permissions in it.

Accepting compressed requests without a size limit

§ 8.1

What it costsThe manual advises limiting the maximum message size precisely because of compression: without a limit, a compressed request unpacks in the node’s memory into anything at all.
How to spot itThe node’s memory consumption grows on small requests.
How to do it rightSet a limit with the size line in the entry point block — that is what it is for.
Compatibility
PowerMTA 6.0
described in the manual of this version · platform: any
The vendor does not state the first version for every directive — there is no note here. The name also appears in the 5.0 edition of the manual.
How to check on the node
Whether the entry point is configured is visible from all three blocks at once; whether it works is checked with a request:
grep -n -A4 '^<transmissions-api' /etc/pmta/config
curl -sS -o /dev/null -w '%{http_code}\n' -H 'Authorization: <key>' https://<node-address>:8585/api/v1/transmissions
A 400 response on a persistent connection usually means not a submission refusal but a missing Content-Length in the request. Missing blocks mean the entry point is not configured at all.
In the Hub
Cost of a mistake
reversible: harms only while it is set
Scenarios
Connect a sending platform · Connection encryption