Repository navigation
feat: add a persistent delayed-send queue with cancel for Gmail - #12
Closed
lucamzanon wants to merge 13 commits into
Closed
lucamzanon wants to merge 13 commits into
lucamzanon wants to merge 13 commits into
Conversation
Behind GMAIL_ALIASES_ENABLED. Reads users.settings.sendAs (covered by the existing gmail.modify grant), offers the primary address plus accepted aliases as read-only identities with stable ids, and lets drafts, MIME imports and submissions use any of them. The identity is re-validated against Gmail right before drafts.send; a removed alias yields forbiddenFrom and keeps the draft. Signatures stay client-side. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Behind GMAIL_SCHEDULE_ENABLED. HOLDFOR/HOLDUNTIL submissions are stored in SQLite instead of being sent; a 5 s worker leases due entries atomically and re-validates identity, draft hash and ledger before drafts.send. Edited/deleted drafts, removed identities and entries that come due while the bridge is down beyond the tolerance are suspended, never sent. undoStatus=canceled cancels pending entries; EmailSubmission/query lists them; interrupted sends are reconciled on restart; /healthz reports per-status counters. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Author
|
Superseded by #14, which consolidates this stack into one reviewable branch. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #11 (
feat/gmail-send-as-aliases); only the last commit is new.What
Behind a new opt-in flag
GMAIL_SCHEDULE_ENABLED=true, the Gmail backend gains a persistent delayed-send queue implementing RFC 4865 FUTURERELEASE, which is exactly what Bulwark's schedule send and undo send speak:maxDelayedSend(GMAIL_MAX_DELAYED_SEND, default 30 days) andsubmissionExtensions.FUTURERELEASE = ["HOLDFOR","HOLDUNTIL"].HOLDFOR/HOLDUNTILis validated exactly like an immediate send (identity re-read from Gmail, draft checked, envelope matched), then stored in SQLite (gmail_schedule: account, identity, recipients, thread, sha256 of the draft MIME, sendAt in UTC) and answered withundoStatus: pending+sendAt. Nothing is sent to Google at that point; the draft stays a native Gmail draft, which is the durable copy.drafts.send, re-checks composition, the identity (fresh send-as read), the draft's existence and hash, and the send ledger. Edited/deleted draft, removed identity, or an entry that came due while the bridge was down beyondGMAIL_SCHEDULE_LATE_TOLERANCE(default 900 s) → suspended, never sent: final, per-recipientdeliveryStatusexplains why, the draft remains in Drafts. Transient errors before the send call leave the entry pending for the next tick.EmailSubmission/set update {undoStatus: "canceled"}cancels atomically while pending (cannotUnsendonce being handed to Google or final);EmailSubmission/querylists newest first. Bulwark's reschedule creates the replacement before cancelling the original, so multiple pending entries per draft are allowed; first to send wins, the others becomecanceled(superseded).drafts.sendmarks the entry uncertain (delivered: unknown) and blocks further sends of that draft through the existing ledger, exactly like immediate sends. Entries left insendingby a crash are reconciled on startup via the ledger.onSuccessUpdateEmail) on a held submission answerforbiddenin the implicitEmail/set: Gmail files the message when it is actually sent (the client treats this as a warning)./healthznow returns per-status queue counters (no addresses or content).With the flag off, behaviour is unchanged (
maxDelayedSend: 0, HOLDFOR rejected).Tests
test/unit/gmail-schedule.spec.ts(10 tests): capability gating, HOLDFOR/HOLDUNTIL validation, queue without network, list/cancel/cannotUnsend, due send once + ledger, suspensions (edited/deleted draft, removed identity), transient retry, late tolerance, uncertain outcome, reschedule/supersede flow, restart reconciliation.refs.spec.ts,search.spec.ts, same on upstream).🤖 Generated with Claude Code