Some applications ingest data through SMTP or LMTP instead of HTTP: a ticketing system that opens a case from an inbound email, an archival service fed over LMTP, a parser that extracts an attachment. The mail provider lets a scenario deliver such a message, then verify the downstream effect through the regular API surface.
If you do not supply a Message-ID header, Tales generates one as <[email protected]>, derived deterministically from the run seed (so identical runs produce identical ids). A supplied Message-ID header is used verbatim. The generated value is always exposed as response.json.message_id. If Date is missing it is set to the current time (non-deterministic, like now_unix).
content_type is optional; it is inferred from the filename extension when omitted, defaulting to application/octet-stream. Attachments produce a multipart/mixed message with base64-encoded parts.
A rejection is not a Tales failure — it is a valid protocol response. The provider distinguishes:
Transport / runtime errors (cannot connect, timeout, TLS handshake, broken session, MIME build, invalid config) → the step fails immediately.
Protocol negative replies (4xx/5xx at MAIL FROM, RCPT, DATA, or the final response) → captured in response.json and assertable with expect.
So expect decides PASS/FAIL: a rejection that matches your assertion is a PASS; a rejection when you asserted accepted = true is an assertion failure (the step fails).
The SMTP password lives in config; it is never copied into reports or error messages.
The report request map carries only metadata (protocol, target, addresses, subject, message-id, masked headers, attachment filename / content-type / size) — never the message body or attachment bytes.
Sensitive headers (Authorization, Cookie, Set-Cookie, *signature*) are masked through the shared header-masking helper.
No IMAP / POP3 reading, no DKIM signing, no SPF / DMARC validation, no MTA / mailbox storage. (The provider can assert a server’s DMARC/policy rejection, but it does not validate DMARC itself.)
SMTP AUTH is PLAIN-only (single-line RFC 4954 form). LMTP has no TLS or auth.
A fresh connection is opened per step.
The Date header and network timing are non-deterministic.
Session-setup failures (connect / greeting / EHLO / LHLO / AUTH) stay fatal — only MAIL FROM / RCPT / DATA / final-response rejections are assertable.
For an SMTP message-final rejection the single reply applies to the whole transaction (recipients.accepted is empty and the detail is the top-level stage = "message"); LMTP reports it per recipient.