logicspike/docs

Communication

Vlozi Email — Mailbox, Newsletter & the delivery engine

A reference for how Vlozi does email. There are two services a seller seesMailbox (addresses & two-way inboxes) and Newsletter (outbound campaigns + sending health/brand) — riding one backend delivery engine (the communication worker: Amazon SES, credit metering, anti-abuse, inbound routing). "Communication" is no longer a dashboard service — its surfaces were redistributed into Mailbox and Newsletter. See §3 for where each piece went.

How to read this. Sections 1–10 describe the target model and how it maps to the code; 11–12 are an open decision + outside reference. A flags an open decision. In diagrams, a dashed arrow marks a planned or optional link, and §9 breaks down what is live vs rolling out vs not built.

Contents

The model

  1. At a glance — two services on one engine + the address model
  2. Navigation map — every page & tab
  3. The delivery engine — the backend + where old Communication pages went
  4. Mailbox service — Inbox · Domains · Mailboxes · Setup
  5. Newsletter service — outbound + absorbed surfaces
  6. Core user flows — verify · create + assign · send · receive
  7. Access & roles — the many-to-many ACL + two permission layers
  8. Under the hood — architecture
  9. Status & notes — live vs rolling out vs planned
  10. Data model — entities across the three DBs

Open + reference 11. mail.vlozi.app mailbox lane — credits-metered (built) 12. Google Workspace reference — proven parallels


1. At a glance

What a seller sees Job Dashboard pages
Mailbox Addresses & two-way mail — verify domains, create mailboxes, run team inboxes with per-person access. Inbox · Domains · Mailboxes · Setup
Newsletter Outbound sending — campaigns, subscribers, templates, plus the sending health, brand & suppression that used to live in Communication. Overview · Campaigns · Subscribers · Segments · Templates · Settings
(backend) delivery engine Not a UI service. The communication worker: SES send, credit metering, anti-abuse guardrails, inbound routing. Both services send through it.

How they relate: Mailbox and Newsletter both send through the delivery engine (which owns SES + billing). Inbound replies flow Cloudflare Email Routing → engine → Mailbox. Verified domains are created in Mailbox and consumed by both.

The address model — one shared default + mailboxes on two domains

Address What it is Scope Send Receive
notify@mail.vlozi.app Shared platform default Every tenant ✅ fallback ❌ send-only
<name>@mail.vlozi.app Mailbox on the shared domain Many · per-user access ✅ (team inbox)
<name>@yourbrand.com Mailbox on your verified domain Many · per-user access ✅ (team inbox)

Both mailbox lanes are the same feature — many addresses, per-person access, full two-way inbox, created by the same flow. The only real differences: your own domain needs verification (the shared one doesn't), and the shared mail.vlozi.app lane is abuse-guarded (reserved slugs, no brand/Vlozi impersonation, plus a quantity cap — see §11). The shared notify@ stays the zero-setup, send-only fallback.


2. Navigation map

There is no "Communication" entry in the sidebar. Both services point at the same backend engine.


3. The delivery engine (backend)

Communication is not a page anymore — it's the backend communication worker that both services call. It owns everything on-the-wire and nothing a seller navigates to:

  • SES send — DKIM-signed delivery from mail.vlozi.app or a verified domain.
  • Credit metering — every billable send is priced + charged.
  • Anti-abuse guardrails — from-ownership, impersonation blocking, suppression, rate limits (the decision tree in §6.4).
  • Inbound routing — Cloudflare Email Routing → email() handler → /internal/inbound into Mailbox.
  • Internal APIs — e.g. GET /internal/sending-domains (verified-domain list the dashboard reads), consumed by Mailbox and Newsletter.

The seller-dashboard still ships a thin modules/comms/api/proxy.ts (/comms/* → gateway) as the client to this engine; the pages that used to sit on top of it moved:

Old Communication surface New home How
Verify domain (SendingDomainsPage) MailboxDomains it's the prerequisite for creating addresses
Sender identity (name · reply-to · From) Mailbox (per-mailbox) a From-address is a mailbox
"Claim alias" (VloziAliasCard) Mailbox mail.vlozi.app is a domain in Create-mailbox
Brand (logo · colors · footer) NewsletterBrand · Footer template branding
Deliverability (health) NewsletterDeliverability bulk-sending health
Suppression (do-not-mail) NewsletterConsent + Suppression sits with list & consent
Message Logs (sent activity) NewsletterActivity the outbound log; mailbox sends also show in threads
Developers (API key + webhooks) Platform/settings/api-keys one scoped key system
Delivery engine (SES · billing · guardrails · inbound) stayscommunication worker unchanged

4. Mailbox service

4.1 Inbox — /dashboard/mailbox

A two-pane console (thread list ∣ conversation). Multi-user and capability-aware.

  • Mailbox switcher: "All inboxes" + one entry per mailbox you can access — filters the thread list and defaults Compose's From.
  • Filters: All / Unread / Archived · search · Compose.
  • Conversation: messages, Reply (only if you have send), Archive / Unarchive (only if you have manage), mark-read on open. Live-polls (list 12 s, open thread 8 s).
  • Working: GET /api/mailbox/v1/threads[?accountId=], /threads/:id, /threads/:id/reply|archive, /compose, /accounts. Replies/compose are billed sends via the delivery engine.
  • Not yet: attachments show a paperclip indicator only — download + storage (R2) is Phase 4; rich HTML bodies also offload to R2 in Phase 4 (today text is stored inline).

4.2 Domains (verify) — /dashboard/mailbox/domains

Where you add and verify a sending domain (moved from Communication — it's step 1 of "set up my email", the prerequisite for creating addresses). Add a domain, publish the DKIM CNAMEs (Cloudflare auto-config or manual), and a DomainVerifier Durable Object polls SES to verification. Newsletter reads this verified-domain list to send campaigns. Flow in §6.1.

4.3 Mailboxes — /dashboard/mailbox/manage

Provision mailboxes and manage who can use them. Gated by mailbox:accounts.manage (non-admins see a "restricted" state).

  • New mailbox → the shared Create-mailbox dialog (create + assign in one). You pick a domain — a verified domain or mail.vlozi.app (the old "claim alias", now just a domain option, subject to the §11 cap) — plus display name and reply-to, then assign teammates with roles.
  • Per-mailbox access panel: current members + their role, Assign member, edit role, revoke.

4.4 Setup (wizard) — /dashboard/mailbox/setup

The guided, first-run journey. Three steps with a stepper + Back/Next.

  1. Address — start fast on @mail.vlozi.app and/or verify your own domain (required for domain mailboxes).
  2. Mailboxes — create mailboxes + assign teammates (same dialog as §4.3).
  3. Review — your addresses, mailboxes, and who's on each → Go to inbox.

5. Newsletter service

Newsletter is the outbound hub: Overview · Campaigns · Subscribers · Segments · Templates · Settings. It absorbs the sending-health/brand surfaces that used to live in Communication — folding into structure it already had (its Settings tabs are Identity · List & consent · Unsubscribe & footer · Schedule · Automation, and it already reads sender settings from the delivery engine via fetchSenderSummary).

Absorbed surface Where in Newsletter
Deliverability (delivery / bounce / complaint health) a Deliverability page — /dashboard/newsletter/deliverability
Suppression (do-not-mail; auto-added by the SES webhook, billed sends skip these) List & consent + Suppression Settings tab
Brand (logo · colors) + compliance footer (postal address + one-click unsubscribe) Brand + Unsubscribe & footer (already present)
Activity (the outbound message log) Overview / Activity
Identity (which address campaigns send from) Identity tab — now a picker over verified mailboxes, not a standalone workspace identity

Newsletter's own docs cover campaigns/subscribers/templates; this section only notes the seams where Communication folded in.


6. Core user flows

6.1 Verify your domain (in Mailbox)

Its lifecycle (a DomainVerifier Durable Object polls SES; the "Check" button polls on demand):

6.2 Create a mailbox + assign a teammate (merged, one action)

Claiming and creating are the same thing now — pick a domain (a verified one, or mail.vlozi.app) and assign people in one flow.

6.3 Send an email (outbound)

6.4 Receive a reply (inbound)

6.5 How a send is resolved & guarded

Which address do we send from? (sender resolution)

The guardrail decision tree (runs in the engine on every send — the anti-abuse core):


7. Access & roles

There are two orthogonal permission layers. Keeping them separate is the whole model — one gates who can administer email, the other gates what you can do with a single inbox.

Layer What it is Set where Gates
Platform RBAC Your workspace role (Owner / Admin / Member) → permissions like mailbox:accounts.manage, comms:send Platform members/roles + API keys Who can administer — see the Mailboxes admin page, verify domains, hold a sending API key
Per-mailbox ACL A grant on one mailbox (Viewer / Agent / Owner), the many-to-many Mailbox → per-mailbox access What you can do with that one inbox — read / send / manage / admin

They compose: mailbox:accounts.manage lets you open the admin page and grant access; the per-mailbox grant is what lets you use a given inbox. An org admin bypasses the ACL for visibility (sees all mailboxes) but the ACL is still the model.

The per-mailbox ACL (the many-to-many)

Access is a many-to-many graph: one person → many mailboxes; one mailbox → many people (shared inboxes). Each grant is a capability set, chosen via friendly presets or the Advanced raw capabilities.

Preset Capabilities Can…
Viewer read see the inbox
Agent read · send · manage reply, compose, archive/organize
Owner read · send · manage · admin all of the above + grant/revoke others
Advanced any combination pick individual capabilities

Which capability unlocks which control:

How visibility is decided (server-side, on every request):

One model, not three. Personal, shared, and delegated inboxes are the same grant graph — Vlozi collapses what Google splits into three separate objects:

Need Google Vlozi
Personal inbox a User (owns 1 mailbox) a mailbox with one grant
Shared inbox (support@) a Group (Collaborative Inbox) a mailbox with many grants
Lend access Delegation feature a mailbox with an extra grant

The UI can still label a mailbox Personal (one grantee) or Shared (many) — derived from the grant count, not a separate object type.

One API key, scoped

There is no separate "comms API key" store. Programmatic sending uses a platform API key created at /dashboard/settings/api-keys, scoped to the permissions it needs (e.g. comms:send). The old "Developers" tab becomes: that scoped platform key + delivery webhooks — nothing comms-private.


8. How it works under the hood

  • The dashboard never talks to workers directly — a Next.js /api/* proxy attaches the user's platform JWT and forwards to the gateway, which validates it and injects x-tenant-id / x-user-id / x-user-permissions before calling the worker.
  • The delivery engine (communication worker) owns SES, billing (credits), suppression, sender resolution, and domain verification internals.
  • Mailbox owns inbox accounts, threads/messages, the per-user access ACL, and the Domains UI; Newsletter owns campaigns + the brand/deliverability/ suppression UI. Both send through the engine.

9. Status & notes

Live

  • Sending domain: user mail goes from mail.vlozi.app (own DKIM = reputation isolation) or a tenant's own verified domain; root vlozi.app is Vlozi-official only.
  • Two-way delivery: replies to a mail.vlozi.app mailbox or a domain mailbox land in the Mailbox inbox.
  • Multi-user mailbox + per-person access, capability-aware inbox, guided Setup wizard, the merged create-and-assign flow.

Rolling out / prerequisites

  • The reshape (this doc): the code moves — Domains → Mailbox nav, Brand / Deliverability / Suppression / Activity → Newsletter, retire the comms sidebar entry — are the redistribution work (Phases 1–3 of the plan).
  • Activation: mailbox and newsletter must be enabled in tenant_services (backfilled for comms tenants) and the user must re-login for them to move from "Coming next" into active services.
  • Pending redeploys: comms (admin provision check via /internal/sending-domains) and manager (new-signup provisioning) still need a deploy to finish activation.

Not built yet (Phase 4+)

  • Attachment download/storage + HTML-body offload to R2 (indicator only today).

Launch gate

  • Amazon SES is still in the ap-south-1 sandbox — sending to arbitrary external inboxes needs production access. Until then sends only reach verified/simulator recipients, so treat the send flows above as functionally-complete but not yet delivering to the open internet.

10. Data model

Three separate Postgres (Neon) databases; userId/tenantId cross-DB references are soft (no FK across databases).

Database Tables
Core (manager) tenants · users · memberships · roles · tenant_services
Comms (delivery engine) comms_sender_settings · comms_tenant_sending_domains · comms_message_logs · comms_message_events · comms_suppression
Mailbox mailbox_accounts · mailbox_account_access · mailbox_threads · mailbox_messages · mailbox_attachments

No schema change from the reshape — the tables and the worker are unchanged; only the dashboard's page ownership moves.


11. mail.vlozi.app is a first-class mailbox lane (built)

mail.vlozi.app is now a real mailbox lane, unified with domain mailboxes: create many (each a mailbox_account, kind alias), per-person access, two-way, shown in Inbox/Mailboxes — created via the same Create-mailbox flow (it's the first, always-available domain option). No DNS/verification (Vlozi owns the domain).

Abuse control = credits (decided). Each mail.vlozi.app mailbox costs MAILBOX_CLAIM_COST (20 credits, tunable) — metered, no hard cap — charged via the comms ledger.charge (same ledger as the old alias claim). Free when the slug is the tenant's already-claimed vloziAlias (they've paid), and free for the system tenant. INSUFFICIENT_CREDITS → 402 surfaced in the create dialog. Reserved-slug + display-name impersonation guards + global slug uniqueness still apply; a verified domain remains the free, own-reputation path.

How it works across services:

  • Provision — mailbox worker POST /accounts accepts mail.vlozi.app without a verified-domain gate; charges via comms /internal/mailbox-charge (create → charge → refund on race), then ensureAccount(kind:"alias") + grants the creator full access.
  • Send — replies/compose from any mail.vlozi.app mailbox go through comms /internal/send-from-mailbox, which runs with trustSenderOwnership so the guard accepts any address the tenant owns (the impersonation block still runs). The public /v1/send path is unchanged (still single-alias gated).
  • Receive — comms inbound resolves the recipient via mailbox /internal/resolve-address (full-address → tenant), falling back to the legacy vloziAlias lookup for un-activated claims.
  • Migration — an existing claimed vloziAlias with no account gets an "Activate as mailbox (free)" action in Setup step 1.

12. Reference — how Google Workspace does it

Google splits the exact same two levels — which validates the model above.

Level 1 · Domain (≈ our Mailbox → Domains): enter domain → verify ownership → turn on Gmail = add MX recordsDKIM/SPF/DMARC. Add more domains later.

Level 2 · Create a user = a mailbox (≈ our Mailbox → Create): Admin → Directory → Users → Add new user → name → username@ + domain dropdown → password → the mailbox is live.

Concept Google Vlozi
Vanity alias user alternate addresses (≤30, same mailbox) a mail.vlozi.app mailbox
Shared inbox a Group (Collaborative Inbox) a mailbox with many grants (§7)
Delegated access mail delegation an extra grant (§7)
Multiple domains verify many, pick per user same

What we borrow: the two-level structure, and the dead-simple name + username@domain create step (ours is even lighter — no password; access = a Vlozi login + a capability grant). Where we differ / win:

  • Many-to-many by default — one grant graph replaces Google's User + Group + Delegation (three concepts collapse into one).
  • mail.vlozi.app quick-start — send with no domain / DNS; Google forces you to own (or buy) a domain first.
  • One engine, two front doors — Mailbox and Newsletter are just two views onto a single delivery engine, instead of separate admin consoles.

Diagrams render with Mermaid (GitHub, VS Code Markdown Preview Mermaid, the docs site). Keep this file in step with apps/seller-dashboard/src/modules/{mailbox,newsletter,comms}.

Communication