mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 10:04:18 +00:00
Wave 3/3a of the compose more-options plan: the two new Message columns, both directions of RFC 8098 read receipts, and the per-alias setting that governs them. Outgoing. MessageSendService::buildEmail() — the single MIME assembly point all three transports share — grows two header pairs. Priority writes X-Priority and Importance together, because a message setting only one is urgent in half the world and ordinary in the other half. A receipt request writes Disposition-Notification-To and Return-Receipt-To, both naming the sending identity rather than the account: mail sent from an alias must have its receipt come back to that alias, or the correlation on the way in has nothing to match — and a request naming anything else is the shape this same codebase refuses to auto-answer. Incoming. ReadReceiptStep queues a scan of each ingested batch; the handler flags the messages whose senders asked, and recognises the ones that ARE receipts. Detection at ingest rather than at read time, because the header bag is already on the row and re-deciding it on every mailbox open puts a security decision on a hot path. Sending. Hung off ThreadStatusUpdater::markRead(), which is the user-initiated path and nothing else. Its inbound twin applyRemoteFlags() deliberately does not call it, so a sync discovering a message was already \Seen elsewhere sends nothing — a receipt claims a person displayed the message, and a sync pass learning about a read that happened last week cannot make that claim. Only genuine null->set transitions fire, so the bulk toolbar re-marking a mixed selection cannot re-send. Async through the bus: opening a mailbox must never block on an outbound SMTP call. The setting is per alias in the existing jsonb bag, no migration, and defaults to Never through ReadReceiptMode::fromSetting() — every unreadable stored shape resolves to silence, so a user who never opens the panel cannot emit a confirmation that their address is live. A Disposition-Notification-To disagreeing with From and Return-Path is pulled back to Ask however the mailbox is configured; not to Never, because the legitimate version of that shape is a bulk sender collecting at its bounce address. Return leg. An inbound MDN stamps readReceiptAt on the message it answers — which is what the sent message renders as "Read at …", without which requesting a receipt tells the user nothing — and the report is marked read and taken off the Inbox rather than deleted. Third column beyond the plan's two: read_receipt_at, in the same migration. The plan asks for "Read at …" on the sent message and there was nowhere to put the timestamp. Verified in a one-off container on the test stack: phpunit 3266 tests OK (baseline 3248, +18 new), composer stan clean with no baseline growth, migration up/down/up clean against the real schema. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>