Subaddressing information lost after delivery

There were really two problems tangled together in this thread: one that was already solved but badly documented, and one that was broken on our side.

The user script side has worked since v0.16.6. This does what you would expect, as long as the recipient was actually rewritten:

require ["envelope", "envelope-dsn", "subaddress", "fileinto", "variables"];

if envelope :matches "orcpt" "rfc822;*" {
    # ${1} is the original recipient, e.g. "[email protected]"
}

if envelope :matches :detail "orcpt" "*" {
    fileinto :create "Inbox/${1}";
}

Two things catch people out. First, :detail and :user need the subaddress extension and orcpt needs envelope-dsn; if either is missing from require, the script does not compile and nothing in it runs, which is exactly why an unconditional envelope :matches "orcpt" "*" looks like it never fires. That is my best guess at what you were hitting in post #10, so it is worth checking whether the script validates at all. Second, the value keeps the address type prefix, so it reads rfc822;[email protected] rather than the bare address. Match it with "rfc822;*", or use :detail, which skips past the prefix.

orcpt is only set when the address was rewritten during RCPT TO: sub-addressing, catch-all, alias resolution, mailing list expansion, or an ORCPT= parameter from the sending server. If the address was delivered unchanged there is nothing to recover, because the full address is already in envelope.to. The script kanashimia posted in #12 is a good template, and stamping the value into X-Original-To so the address test can be used on it is a sensible pattern.

The system script side is where the issue was. The advice in post #3, to pull the detail out in a DATA stage script and add a header, could never have worked: by the time DATA runs, recipients have already been rewritten to their final address, and the original was not handed to the script at all. That is fixed for the next release. envelope.orcpt is now available at the DATA stage as an array that lines up index for index with envelope.to, so this works:

require ["envelope", "envelope-dsn", "editheader", "variables"];

if envelope :matches :detail "orcpt" "*" {
    addheader "X-Original-Detail" "${1}";
}

If a transaction has more than one recipient, use the expression form to correlate the two lists, since the envelope test flattens arrays into a single value:

let "original" "envelope.orcpt[0]";
let "final" "envelope.to[0]";

The array is only present when at least one recipient in the transaction was rewritten, matching the behaviour of user scripts.

One clarification on post #4: headers added from a RCPT TO stage script are discarded because there is no message at that point in the transaction. addheader and deleteheader only take effect in DATA stage scripts, and setenvelope is the only modification that applies at the MAIL FROM and RCPT TO stages.

On the RFC 6009 point, kanashimia is right. The spec says it is an error to combine an ADDRESS-PART argument such as :detail with the DSN envelope parts, since they do not have address syntax. Stalwart accepts it anyway and applies the address parsing after the type prefix, because it is by far the most convenient way to reach the detail. That is a deliberate deviation, and it will be documented as one rather than removed, since scripts already depend on it.

A few related problems turned up while tracing this and are being handled separately: recipients rewritten through the RCPT TO rewrite expression, through setenvelope "to" in a system script, or through the queue expansion path do not record the original address, so orcpt stays empty in those setups even though a rewrite took place; the ORCPT value supplied by a remote server is xtext encoded and we do not decode it before handing it to Sieve, so a + in the original address shows up as +2B; and the Original-Recipient field in delivery status notifications repeats the rfc822; prefix. The Sieve variables documentation is also out of date, as it still says user scripts can only see From and To, which stopped being true in v0.16.6.