Tracking: Sieve-driven email publishing to permissioned ATProto spaces

open
#7755c5e opened by agent Sep 21

Tracking: Sieve-driven email publishing to permissioned ATProto spaces

Design intent

Enable user-directed email workflows: a user authors a Sieve script that matches emails they want to share, and on match the email is saved (JMAP JSON form) as a record in a permissioned ATProto data space using the user’s credentials.

Example: [email protected] publishes all inbound mail as records in a shared space; an external help-desk app syncs the space, creates cases, assigns workers.

This issue tracks the overarching design. It is the basis for a follow-up design session to decompose into smaller tasks. Not all decisions need to be locked here.

Proposal (recommended architecture)

Signal + bridge publisher. No custom Sieve action, no Stalwart fork.

  • User sieve (untrusted, delivery-time) stays portable standard Sieve and only signals: fileinto a designated mailbox and/or addflag.
  • A sovrn publisher daemon watches via JMAP (push/poll on the signal mailbox), fetches Email/get JMAP JSON via the service credential (+ Impersonate), and writes at.sovrn.mail.archive (name TBD) into the target permissioned space using the user’s delegated PDS grant.
  • “With whom” is controlled by space membership (com.atproto.simplespace member list), not by raw addresses in Sieve.

Rationale:

  • Untrusted user scripts cannot exec/HTTP by design; only admin trusted scripts can. Giving users trusted-script power breaks isolation.
  • A vnd.sovrn.* Sieve action would require forking stalwartlabs/sieve (grammar + bytecode + runtime + host loops) with AGPL + upkeep cost. Prefer HTTP-level integration against unmodified Stalwart (see docs/05-stalwart-integration.md licensing posture).
  • Pre-queue (milter/MTA hook) would publish spam/rejected mail; post-delivery JMAP watch is the correct stage.

User-control requirements (locked direction)

  • Match logic is 100% user Sieve. Bridge never re-interprets it.
  • User picks a human-meaningful mailbox/space name (e.g. Shared/<name> or Space/<name>, exact prefix TBD) whose purpose is obvious from the name they chose. The name maps to an opaque space skey underneath so at:// URIs leak no names.
  • Revocation is user-side: edit script, remove fileinto, leave space, or revoke the PDS grant. All stop publishing without operator action.
  • App-view validates the script on save: only allowlisted publish targets permitted; audit-log every publish-target change.

Follow-up decisions (to lock before spec)

  1. Signal naming + mapping (user input: user controls the name). Decide Shared/<name> vs Space/<name>, allowed charset/length, mapping name -> skey, rename semantics, per-user vs per-domain namespaces.
  2. Redaction / payload shape (user input: curated subset first). Define the redaction list as part of this work. Initial direction: keep only a curated subset (messageId, from/to/date/subject, thread refs, text body snippet?, attachment metadata not content?) rather than full JMAP JSON. Decide size caps, attachment/blob-ref policy, header allowlist/blocklist, what the help-desk MVP needs.
  3. Credential scope (user input: likely standard permissionSet). Decide: new OAuth scope profile (repo:at.sovrn.mail.archive?action=create) vs bundling into the standard permissionSet. Covers incremental re-auth UX, refresh storage (device_grants-like), TTL, revocation fan-out. See docs/02-identity-and-auth.md scopes + docs/04-spaces.md credential chain.
  4. Space topology. Per-share space (skey = share ID) vs reuse of domain space? Recommendation: per-share space to isolate help-desk membership. Needs new space type (e.g. at.sovrn.space.inbox) + record lexicon (e.g. at.sovrn.mail.archive).
  5. Author/reader invariant. Confirm authorDid == mailbox-owner DID filter on the reader side; allowlist model for the external app.
  6. Publisher mechanics. JMAP EventSource/WS vs poll fallback, journal + content-hash dedupe (Message-ID -> rkey), retry/backoff, per-user rate caps, loop guard header, failure never blocks delivery.
  7. Erasure propagation. Mailbox/record delete -> space record delete + member-rotation story per docs/04-spaces.md erasure section; residual-copy disclosure.
  8. Sieve save-time validation UX. Where the allowlisted-target check lives (app-view XRPC vs manage path), error copy, audit log schema.

Candidate decomposition (for design session)

  • S-e1: space + record lexicons (at.sovrn.space.inbox, at.sovrn.mail.archive).
  • S-e2: permission/scope decision + grant storage + re-auth UX.
  • S-e3: Sieve signal contract + save-time validation + audit log.
  • S-e4: publisher daemon (watch -> fetch -> redact -> publish -> dedupe/retry).
  • S-e5: redaction list + payload fixtures.
  • S-e6: erasure + revocation e2e test.
  • S-e7: help-desk reference consumer doc.

References

  • docs/04-spaces.md (permissioned spaces, member-list v1, publisher/reader).
  • docs/02-identity-and-auth.md (DPoP-bound tokens unusable cross-service; brokered grants).
  • docs/05-stalwart-integration.md (Registry/JMAP-only control plane, no fork).
  • Prior finding: Stalwart untrusted Sieve has no pipe/exec; trusted-only exec/query/http_header not suitable for user-authored scripts.