The payload listed attachments but not their contents. A receiver told an invoice exists can't do anything with it, and digging the part back out of the base64 raw source means reimplementing a MIME parser at the other end — the work this payload exists to avoid.
Attachments now carry their bytes alongside the existing metadata:
"attachments": [{
"id": "2",
"filename": "invoice.pdf",
"contentType": "application/pdf",
"size": 84213,
"included": true,
"encoding": "base64",
"content": "JVBERi0xLjQK…"
}]Bounded on purpose. Base64 adds a third on top, and a mailbox accepting 25 MB of attachments would otherwise produce ~33 MB of JSON per message. Defaults are 10 MB per attachment (WEBHOOK_MAX_ATTACHMENT_BYTES) and 20 MB per message (WEBHOOK_MAX_ATTACHMENTS_TOTAL_BYTES); WEBHOOK_INCLUDE_ATTACHMENTS=false turns them off entirely.
Anything over a cap is still listed, with included: false and an omittedReason — silence would be indistinguishable from a message that had no attachments, which is exactly the kind of gap that goes unnoticed for months.
345 tests, 344 passing, 1 skipped.