Send the read receipt the sender asked for

JMAP has an extension for this -- RFC 9007's MDN/send -- and Stalwart does
not implement it, so ihasmail assembles the RFC 8098 multipart/report itself
and sends it the long way round: raw MIME uploaded as a blob, imported,
submitted. That is also why the receipt lands in Sent, which is where it
honestly belongs.

The plumbing is the easy half. A receipt tells whoever asked that the address
is live and when the message was read, to an address the sender chose, so the
refusals are the feature: nothing marked Auto-Submitted (RFC 3834, or two
servers answer each other forever), nothing carrying Precedence bulk/list/junk
or a List-Id, nothing already acknowledged, nothing that never arrived. A
receipt aimed anywhere other than the sender is offered, but says so first.
There is no "always send" setting, only ask or never.

Sending is recorded with RFC 3503's $mdnsent keyword on the original rather
than remembered locally, so a second look -- or another client entirely --
knows not to ask again. Non-ASCII parts go base64 rather than 8bit, so
nothing rests on 8BITMIME surviving every hop.

Verified against the mock end to end: the blob uploads, the receipt imports
and submits, and the original reads back marked. Not yet exercised against
the live server.
This commit is contained in:
2026-08-24 22:53:06 -07:00
parent be75a02181
commit 3310149fcc
10 changed files with 570 additions and 4 deletions
+2
View File
@@ -490,6 +490,8 @@ img { max-width: 100%; }
.remote-banner button { color: inherit; font-weight: 700; text-decoration: underline; }
.scheduled-banner { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; padding: 8px 12px; background: var(--accent-soft); color: var(--accent-soft-fg); border-radius: var(--radius-sm); font-size: .9em; }
.scheduled-banner button { color: inherit; font-weight: 700; text-decoration: underline; }
.receipt-banner { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; padding: 8px 12px; background: var(--warn-soft); color: var(--warn); border-radius: var(--radius-sm); font-size: .9em; }
.receipt-banner button { color: inherit; font-weight: 700; text-decoration: underline; }
.quote-toggle { display: inline-flex; align-items: center; gap: 4px; margin: 8px 0; padding: 2px 10px; border-radius: 999px; background: var(--bg-sunken); color: var(--fg-muted); font-size: 12px; border: 1px solid var(--border); }
.quote-toggle:hover { background: var(--bg-active); }
.attachments { display: flex; flex-wrap: wrap; gap: 10px; padding: 4px 16px 16px; }