What happens
splitRecipients (lib/email-composer-utils.ts) tracks inAngle as a latch: < sets it, only > clears it. A recipient string that opens an angle bracket without closing it stays "inside an address" for the rest of the string, so every separator after that point is kept literal and the whole list collapses into a single entry.
Reproduce
Paste this into the To field:
Ap Reinders <ap@x.com, Erwin Beets <erwin@x.com, jaco@x.com
splitPastedRecipients returns:
valid: [{ email: "jaco@x.com" }]
invalid: ["Ap", "Reinders", "<ap@x.com,", "Erwin", "Beets", "<erwin@x.com,"]
ap@ and erwin@ are dropped silently. The whitespace fallback tokenizes them with a trailing comma still attached, so isValidEmail rejects them.
Why it reaches the wire
The invalid leftovers are joined back into the input field. commitCurrentInput (components/email/email-composer.tsx) then accepts whatever parseRecipient returns without validating it, so a chip whose email is the entire pasted list goes into Email/set. The server writes it as one mailbox:
To: <Ap Reinders <ap@x.com, Erwin Beets <erwin@x.com, jaco@x.com>, "Erwin Beets" <erwin@x.com>, ...
The envelope recipients are well formed, so RCPT TO succeeds and the relay only rejects at DATA:
host 'smtp.lettermint.co' rejected command 'DATA' with code 550 (0.0.0) 'Invalid To email address'
Every recipient bounces, with an error message that points at the addresses rather than at the header. The addresses are all valid and deliverable.
Environment
Bulwark 1.7.7, Stalwart 0.16.13, Lettermint as outbound relay. Verified still present on current main (1.8.1).
Related
#672 fixed the mailbox-level parsing so splitMailbox tolerates a missing >. This is the list-level tokenizer one layer up, which still folds on the same input.
A separate defect makes this reach the server at all: the composer commits and sends an unvalidated recipient. Filed separately.
What happens
splitRecipients(lib/email-composer-utils.ts) tracksinAngleas a latch:<sets it, only>clears it. A recipient string that opens an angle bracket without closing it stays "inside an address" for the rest of the string, so every separator after that point is kept literal and the whole list collapses into a single entry.Reproduce
Paste this into the To field:
splitPastedRecipientsreturns:ap@anderwin@are dropped silently. The whitespace fallback tokenizes them with a trailing comma still attached, soisValidEmailrejects them.Why it reaches the wire
The invalid leftovers are joined back into the input field.
commitCurrentInput(components/email/email-composer.tsx) then accepts whateverparseRecipientreturns without validating it, so a chip whoseemailis the entire pasted list goes intoEmail/set. The server writes it as one mailbox:The envelope recipients are well formed, so
RCPT TOsucceeds and the relay only rejects atDATA:Every recipient bounces, with an error message that points at the addresses rather than at the header. The addresses are all valid and deliverable.
Environment
Bulwark 1.7.7, Stalwart 0.16.13, Lettermint as outbound relay. Verified still present on current
main(1.8.1).Related
#672 fixed the mailbox-level parsing so
splitMailboxtolerates a missing>. This is the list-level tokenizer one layer up, which still folds on the same input.A separate defect makes this reach the server at all: the composer commits and sends an unvalidated recipient. Filed separately.