logicspike/docs

Newsletter

Newsletter Service — Developer Guide

Last Updated: 2026-06-28 Status: Active

Complete internal reference for the Vlozi newsletter system. Everything a developer needs — architecture, schema, API, billing, templates — lives here or is one link away.


1. What Is This System?

A multi-tenant email marketing engine built as a Cloudflare Worker. Tenants create templates, manage subscribers, build segments, and fire campaigns. Blog publish events auto-trigger newsletter sends via a binding configuration.

Codebase Location Role
newsletter-service apps/newsletter-service CF Worker — owns all newsletter data, send queue, scheduled campaigns
seller-dashboard apps/seller-dashboard/src/modules/newsletter Next.js UI — template editor, subscriber management, campaign builder
communication-service apps/communication CF Worker — email provider abstraction (Resend); handles bounce webhooks

2. Start Here By Role

Role Reading Path
New to newsletter This doc → architecture.mddatabase.md
Backend / newsletter-service work architecture.mddatabase.mdapi-spec.mdpermissions.md
Adding or changing templates templates.md — authoritative variable contract
Billing or credit questions billing.md
Known issues / next sprint backlog.md

3. Doc Index

Doc What It Covers
architecture.md System diagram, services, request flows, queue pipeline, blog integration
database.md All 8 tables — columns, indexes, business rules, migration history
api-spec.md Every route — method, path, auth, request/response shapes
templates.md Template variable contract, HTML escaping, snapshot semantics, render version
billing.md Credit model (1cr = 5 emails), upfront charge, idempotency, failure modes
permissions.md Every permission string, what it gates, role bundles
backlog.md Known bugs, gaps, and improvement scope with fix guidance

4. Codebase Quick Map

apps/
├── newsletter-service/               CF Worker · port 8795 (local)
│   ├── src/index.ts                  Entry point — Hono app, queue handler, DO export
│   ├── src/scheduler.ts              CampaignScheduler Durable Object (alarm-based scheduling)
│   ├── src/db/schema.ts              8 tables across 9 migrations
│   ├── src/routes/                   subscribers · campaigns · templates · sections · segments
│   │                                 blog-binding · public · internal · internal-blog
│   ├── src/lib/campaign-send.ts      Core send logic: resolve recipients → charge credits → enqueue
│   ├── src/lib/render.ts             Template variable substitution ({{ subscriber.name }}, etc.)
│   ├── src/lib/hmac.ts               HMAC token sign/verify for confirm & unsubscribe URLs
│   └── drizzle/                      0000–0008 SQL migrations

└── seller-dashboard/
    └── src/modules/newsletter/       Campaign builder, template editor, subscriber dashboard

5. Four Things Every Dev Must Know

1. Sends go through a Cloudflare Queue, not inline. fireCampaign() enqueues one job per subscriber. The queue consumer calls comms-service for each delivery. This means a 50k-recipient campaign is 50k queue messages — jobs are processed at CF Workers throughput limits, not in a loop inside a single request.

2. Credits are charged upfront, before enqueue. Math.ceil(recipients / 5) credits are debited from Core DB before a single message is queued. If the charge fails (402), no emails are sent and nothing is in the queue. If send fails after enqueue, credits are NOT automatically refunded — this is intentional (comms-service was called).

3. Every subscriber has a global suppression check. nl_suppression_list is cross-tenant. A hard bounce on tenant A suppresses the address for all tenants. This is non-negotiable for inbox reputation. The recipient query always excludes suppressed addresses.

4. Blog integration is opt-in per tenant, fire-and-forget. blog-service calls /internal/blog/post-published after a successful publish. If newsletter-service errors or no binding is configured (204), the publish still succeeds. Newsletter dispatch is a side effect — it cannot block or roll back publishing.

Newsletter