A reference for how Vlozi does email. There are two services a seller sees — Mailbox (addresses & two-way inboxes) and Newsletter (outbound campaigns + sending health/brand) — riding one backend delivery engine (the
communicationworker: 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
- At a glance — two services on one engine + the address model
- Navigation map — every page & tab
- The delivery engine — the backend + where old Communication pages went
- Mailbox service — Inbox · Domains · Mailboxes · Setup
- Newsletter service — outbound + absorbed surfaces
- Core user flows — verify · create + assign · send · receive
- Access & roles — the many-to-many ACL + two permission layers
- Under the hood — architecture
- Status & notes — live vs rolling out vs planned
- 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.appor 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/inboundinto 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) |
Mailbox → Domains | 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) | Newsletter → Brand · Footer | template branding |
| Deliverability (health) | Newsletter → Deliverability | bulk-sending health |
| Suppression (do-not-mail) | Newsletter → Consent + Suppression | sits with list & consent |
| Message Logs (sent activity) | Newsletter → Activity | 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) | stays — communication 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 havemanage), 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.
- Address — start fast on
@mail.vlozi.appand/or verify your own domain (required for domain mailboxes). - Mailboxes — create mailboxes + assign teammates (same dialog as §4.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 | 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 injectsx-tenant-id/x-user-id/x-user-permissionsbefore calling the worker. - The delivery engine (
communicationworker) 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; rootvlozi.appis Vlozi-official only. - Two-way delivery: replies to a
mail.vlozi.appmailbox 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
commssidebar entry — are the redistribution work (Phases 1–3 of the plan). - Activation:
mailboxandnewslettermust be enabled intenant_services(backfilled forcommstenants) 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) andmanager(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 /accountsacceptsmail.vlozi.appwithout a verified-domain gate; charges via comms/internal/mailbox-charge(create → charge → refund on race), thenensureAccount(kind:"alias")+ grants the creator full access. - Send — replies/compose from any
mail.vlozi.appmailbox go through comms/internal/send-from-mailbox, which runs withtrustSenderOwnershipso the guard accepts any address the tenant owns (the impersonation block still runs). The public/v1/sendpath is unchanged (still single-alias gated). - Receive — comms inbound resolves the recipient via mailbox
/internal/resolve-address(full-address → tenant), falling back to the legacyvloziAliaslookup for un-activated claims. - Migration — an existing claimed
vloziAliaswith 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 records → DKIM/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 | 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.appquick-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}.