Knowledge base

Selecting a sending address by message content

<pattern-list>
A routing layer between “what the message looks like” and “where it will go out from”

A list of rules by which the node decides which address to send a message from. It looks at the message itself: a header, the envelope return address or the recipient address.

Three matchers, one syntax. A directive, an optional parameter, a regular expression (case does not matter) and a result. The result is usually the name of a sending address, which is why the list turns out to be a routing layer between what the message looks like and where it leaves from. There is also another kind of result — recipient priority.

Lists nest inside each other. Instead of an answer, a rule can hand the parsing over to another list — you get a decision tree right in the configuration: one list splits the stream into groups, the next ones sort things out within each.

The recipient matcher has a caveat. When there are several recipients and they match different rules, a message cannot leave from two addresses at once — the manual describes how this is resolved.

This is not the list that reacts to the receiver. The similarly named <smtp-pattern-list> is about SMTP responses during delivery and backoff mode. This one is about the outbound choice before sending.

The main trap is confusing two similar names. pattern-list selects the sending address by message content and lives in the submission block; smtp-pattern-list reacts to receiver responses and lives in the recipient block. Someone looking in the configuration for a reaction to refusals who finds the first name will decide it is configured. Our configuration has both, in different blocks: pattern-list on the submission sources, smtp-pattern-list blocking-errors on the recipient domains.
What the Hub does. It writes it — and the whole sending layout rests on this list. A rule is created for each “domain and address” pair: the message’s return address is matched against the domain, and the message leaves from the sending address assigned to it.

That way one submission port serves any number of domains: a platform submits a message with the return address of its domain, and the node itself directs it to the right sending address. Neither the platform nor the port needs to know about addresses.

The reference to the list sits in the submission block, next to the default-virtual-mta default — that is what fires when no rule matched.

Recommended values

What to select the sending address by — three matchers:

Matcher What it looks at When it fits
by return address the envelope address our case: requires nothing from the submitting side
by header any message header when the submitting system can set them
by recipient the recipient address rarely: a message often has many recipients

The return address is the most reliable choice. Every message has one, and it does not depend on whether the platform can set headers.

Write the expression with an end anchor. A rule like /@domain$/ will match the domain, while without the anchor it also matches any address with this domain inside. That is exactly why the Hub escapes the domain and adds an anchor.

The default is mandatory. A message that matched no rule will go out from the default address — and if there is none, you will have to explain why the message did not go out.

The matcher table and the anchor advice are ours. From the manual here come the set of three matchers, the single rule syntax, list nesting and the caveat about several recipients.
Example configuration
# a rule for each “domain → sending address” pair
<pattern-list pmta-pattern>
    mail-from /@example\.com$/ virtual-mta=mta-news-1    mail-from /@shop\.example\.net$/ virtual-mta=mta-shop-1
</pattern-list>
<source {pmta-auth}>
    pattern-list pmta-pattern
    default-virtual-mta pmta-pool
</source>
The highlighted line is what the Hub writes for each domain. The end anchor is mandatory: without it the rule would also catch @example.com.other.net. The last line catches everything that matched no rule.
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▶

Writing an expression without an end anchor

§ 3.3.14

What it costsThe rule /@example.com/ will match both @example-com.net and @example.com.attacker.net. Messages will leave from someone else’s sending address, and this can be noticed only in accounting.
How to spot itIn accounting a domain’s messages leave from an address assigned to another domain.
How to do it rightAdd an end anchor and escape the dots in the domain name — the Hub does this when generating.

Taking this list for a reaction to refusals

§ 3.3.15

What it costsThe names differ by one word, and they belong to opposite sides of the node. Having found pattern-list in the configuration, people decide that the reaction to receiver responses is configured — while it is not.
How to spot itQueues never go into backoff mode, even though the pattern list “is there” in the configuration.
How to do it rightLook at the full name and the block where the reference sits. The reaction to the receiver is only smtp-pattern-list.

Doing without a default address

§ 3.3.14

What it costsA message that matched no rule is left without a sending address. A new domain appearing on the platform, for which no rule has been written yet, turns into an obscure refusal.
How to spot itSome messages do not go out after a domain is added, and the log complains about sending address selection.
How to do it rightKeep default-virtual-mta in the submission block filled in. It is the safety net for a new domain.
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.
Inheritance
The list itself lives at the root of the file and is not inherited. What is inherited is the reference to it — the pattern-list line in the submission block, by the usual source merging rule: a narrow block overrides a broad one. So different submitting systems can have different lists. Nested lists are processed in call order, not merge order.
How to check on the node
Check not the list but which address the message left from:
grep -n -A10 '^<pattern-list' /etc/pmta/config
pmta show vmtas
The first shows the rules and their order. The second shows which sending addresses are actually busy: if messages leave from the wrong addresses, the cause is usually an expression without an anchor or a missing rule, and then the default fired.
In the Hub
Cost of a mistake
reversible: harms only while it is set
Scenarios
Sending address rotation · Connect a sending platform