This is very informative. The code confirms what X-Ryl669 is reporting: when subaddressing resolves (line ~201-218), the original foo+bar@baz address is replaced with the rewritten foo@baz address in the session’s rcpt_to list. The original address is saved only in dsn_info (as rfc822;...), not preserved in the envelope for Sieve. Let me check if the DATA stage / user Sieve delivery has access to the original address at all.
This is very revealing. Looking at the source code directly:
When subaddressing resolves, rcpt_resolve() returns RcptResolution::Rewrite(address) with the stripped address (e.g. foo@baz). Then in rcpt.rs lines 200-217, the original address is popped and replaced with the new SessionAddress for foo@baz. The original foo+bar@baz address is lost from rcpt_to at that point.
When build_script_parameters() builds the Sieve envelope for the DATA stage (lines 106-111), it iterates over self.data.rcpt_to which by then only contains the rewritten address (foo@baz). So envelope.to in both server-side DATA scripts and user scripts sees only the stripped address – the +bar detail is gone by the time any Sieve script runs at DATA time.
This confirms that X-Ryl669’s findings are correct: this is a genuine code-level regression/bug. The sub-address detail is discarded from the in-session recipient list when RcptResolution::Rewrite fires, and there is no mechanism currently preserving it for later Sieve access.
The RCPT-stage server Sieve runs before rcpt_resolve() is called (see rcpt.rs lines ~108-135), so at that point envelope.to still has the original foo+bar@baz. However, adding a header at RCPT stage does not persist to the message (headers are message-level, added at DATA stage), so it is useless there.
The only currently working workaround is the regex approach on the To: message header that X-Ryl669 already shared – matching against the To header in the user Sieve script, since the To: header in the message body is not modified by Stalwart’s address resolution.
To be completely transparent: this appears to be a known bug/gap in current Stalwart. The sub-address detail (+bar) is not preserved after RCPT resolution rewrites the address, so it is unavailable to envelope :detail or envelope.to at delivery-time Sieve. The To: header workaround is the only viable path until this is addressed in the server code.
This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong. If you found it useful, mention @helpbot in any reply to ask follow-up questions and the bot will respond again.