Skip to content

Keep member mail out of Spam, and let sync survive a vanished message - #2

Open
nam-jaehyun wants to merge 1 commit into
mainfrom
keep-member-mail-out-of-spam
Open

nam-jaehyun wants to merge 1 commit into
mainfrom
keep-member-mail-out-of-spam

Conversation

@nam-jaehyun

Copy link
Copy Markdown
Member

Why

Legitimate mail to a member address was landing in Spam, and pressing Not spam did not make it stick.

A provisioned member address is a Google Group with the shared mailbox as its only member, so every message takes a second hop. That hop breaks SPF alignment and rewrites the envelope, so the spam classifier sees mail whose authentication does not line up with its From. Gmail's per-sender "not spam" decision keys on the sender, not on the path — the next message arrives the same way and is judged the same way.

Separately, sync had stopped entirely. messages.get returned 404 for a message that was gone between the history record and the fetch (deleted in Gmail, or swept from Spam), that rejection failed the whole batch, and the history cursor stayed parked on it. No new mail arrived at all; /healthz reported lastError: "Requested entity was not found.".

What changed

  • ensureNeverSpam() in src/google/provisioning.ts creates one Gmail filter per member address matching list:<local>.<domain> — the list id the group stamps on every redistribution — with SPAM removed. It is idempotent: existing filters are read back and matched on the query before creating anything.
  • provisionMember() calls it after createGroupAlias(), non-fatally. A member is still provisioned if the filter call fails.
  • exemptMembersOnce() in src/web/server.ts runs the same exemption over already-active members on the first sync tick, so upgrading does not mean re-approving anyone. It retries on the next tick if it throws.
  • fetchAndStore() in src/google/sync.ts skips a message whose messages.get returns 404 instead of failing the batch.

Verification

Deployed and confirmed against the live tenant: six filters present (list:support.boanlab.com, list:admin.boanlab.com, list:contact.boanlab.com, and the three kloud.zone equivalents), and /healthz back to {"ok":true,"connected":true,"sync":{"ok":true,"staleSeconds":7,"consecutiveFailures":0,"lastError":null}} with mail arriving again.

tsc --noEmit clean; 256/256 tests pass.

Note

The filter suppresses Gmail's own classification on the way in. It does not fix authentication, so mail sent from a member address still depends on domain DKIM being configured — that requirement is already in docs/deployment.md.

Mail redistributed by a Google Group loses SPF and DKIM alignment on the
second hop, so legitimate mail to a member address was classified as spam --
and marking it "not spam" in Gmail did not stick, because the next message
arrived through the same path and was judged the same way.

Provisioning now creates a Gmail filter per member address matching
list:<local>.<domain>, the list id the group stamps on every redistribution,
with SPAM removed. Existing members are exempted once per process on the
first sync tick, so an upgrade does not require re-approving anyone.

Separately, messages.get returning 404 -- a message deleted or swept between
the history record and the fetch -- failed the whole batch and left the sync
cursor stuck on it, stopping delivery entirely. Skip those.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant