Last updated: 2026-07-04 (Flows modernization + full revamp shipped) Analyst: Codebase audit (full source + spec + test review)
⚠️ Much of the original backlog below is now RESOLVED. The service was made a full Vlozi Flows citizen and then went through a 4-phase revamp (correctness → hardening → deliverability → quality). See Revamp Status immediately below for what's fixed vs. still open before reading the (historical) Bugs / Areas of Improvement sections.
Revamp Status (2026-07)
Flows modernization (69b2b99): comms emits comms.email.sent/failed/delivered/ bounced/complained + comms.alias.claimed; exposes comms.* MCP tools
(send_message, claim_alias, list_logs, get_analytics, check_alias_availability,
get/update_sender_settings) + 6 trigger descriptors; on the mcp-gateway.
Revamp — Phase 1 correctness (4272f73), Phase 2 hardening (d86b05d),
Phase 3 deliverability (5d55bf8), Phase 4 quality:
| Backlog item | Status |
|---|---|
| B1 unverified fromEmail silent 403 | Fixed — pre-dispatch DOMAIN_NOT_VERIFIED (422) guardrail (verified-domain set, env-overridable). Full self-serve Resend Domains flow still deferred. |
| B2 no retry | Fixed — sendViaResend bounded retry on 429/5xx/network + Resend Idempotency-Key so retries can't double-send. |
| B3 webhook replay double-counts | Fixed — dedup on the Svix event id (provider_event_id unique) + replay-window + best-effort side-effects on redelivery. |
| B4 messageEvents no FK / mislabel | Open (documented) — message_id holds the provider id, not an FK; comment corrected; column rename is a Phase-3 migration candidate. |
| B5 SMS fake "sent" | Fixed — honest 501. |
| B6 sender_email not validated | Fixed — .email() on sender/from; recipient .email() on the email channel. |
| B7 db handle cached by URL | Fixed (guard) — getCoreDb asserts CORE_DATABASE_URL != COMMS_DATABASE_URL. Full core-schema rebind deferred. |
| B8 alias race 409 UX | Open — server race-safe; dashboard UX note only. |
| A1 rate limiting | Fixed — native SEND_LIMITER on /v1/send. |
| A2 backoff + DLQ | Partial — retry shipped; DLQ deferred (account at Cloudflare Queues limit; code binding-guarded). |
| A3 domain verification | Partial — guardrail shipped (B1); full self-serve flow deferred. |
| A4 webhook dedup | Fixed (see B3). |
| A5 audit log | Open. |
| A6 enrich /v1/logs with events | Fixed — /v1/logs + MCP list_logs join the latest delivery outcome; new GET /v1/stats + MCP get_analytics. |
| A7 SMS via Twilio | Fixed as 501 — honest rejection; real Twilio deferred. |
| (new) suppression list | Fixed — comms_suppression (migration 0005): webhook records hard-bounce/complaint; dispatchSend short-circuits to a suppressed status. |
| (new) onError exception leak | Fixed — generic message; stack logged server-side. |
| (new) Svix timing oracle / replay | Fixed — constant-time compare + ±5min timestamp window. |
| (new) input size caps | Fixed — html/text/metadata caps; content refine; emailHeaders reserved-key guard. |
| (new) idempotent-replay over-bill | Fixed — cached hit returns deduped; accumulator skipped. |
Still open / deferred: full self-serve domain verification (Resend Domains API +
tenant_sending_domains), real SMS/WhatsApp + multi-provider/BYOP, batch + scheduled
sends, audit log (A5), DLQ (queue-limit-blocked), List-Unsubscribe defaulting (needs a
routed unsubscribe mailbox), the fuller index.ts routes/services split (pure leaf
modules lib/resend.ts + lib/svix.ts + lib/text.ts are extracted).
Scorecard
| # | Parameter | Score | Notes |
|---|---|---|---|
| 1 | Testing | 9.5 | 73 test cases across 7 files; cross-tenant isolation, idempotency, alias race condition, gateway key rotation, Svix verification all covered |
| 2 | Multi-tenancy | 9.5 | All queries scoped by tenant_id; alias uniqueness is global across tenants; tests formally prove cross-tenant non-leakage |
| 3 | Billing Integration | 8.5 | Credit gate on /v1/send; accumulator pattern (200 thousandths/email = 5 emails per credit); alias claim charges 20 credits with idempotency key + refund on race |
| 4 | Business Logic | 8.5 | Alias claim with credit charge + unique constraint race-safety + loser-refund; send idempotency; Svix webhook fan-out to newsletter (engagement + bounce kill-switch) |
| 5 | Infrastructure | 8.5 | Service binding to newsletter worker (trusted path); dual Neon databases (comms + core); Workers-compatible Svix HMAC without SDK |
| 6 | Data Model | 8.5 | Clean 3-table schema (logs, settings, events); proper indexes on all filter columns; event table decoupled from log table |
| 7 | API Design | 8.0 | Consistent Hono + Zod patterns; correct HTTP status codes; partial implementation (alias + sender settings + send + webhook + logs) |
| 8 | Performance | 8.0 | Edge Worker execution; indexed idempotency key lookup; idempotency short-circuit avoids re-calling Resend |
| 9 | Security | 7.5 | Gateway key rotation (comma-separated); Svix HMAC-SHA256 verification; INTERNAL_KEY for sibling workers; no rate limiting; no audit log |
| 10 | Error Handling | 7.0 | Hono global error middleware; Zod parse errors surface cleanly; no retry logic — Resend network failure = permanent failure |
| 11 | Feature Completeness | 6.0 | Core send + sender settings + alias + webhook + logs working; SMS is console stub; no batch, no multi-provider, no scheduled sends, no domain verification |
| 12 | Observability | 5.5 | comms_message_logs provides operational history; comms_message_events records delivery feedback; no metrics, no tracing, no alerting |
| 13 | Provider Integration | 5.0 | Resend only (email); console fallback; SMS acknowledged but never delivered; no BYOP, no provider management API |
| 14 | Documentation | 5.0 | 8 internal design docs; api-spec and domain-model are aspirational/stale; implementation-plan is 40% accurate; no public docs |
Overall: 7.5 / 10
Bugs
B1 — Unverified fromEmail silently fails at Resend with no user explanation
Severity: High
Location: src/index.ts → dispatchSend(), PUT /v1/sender-settings
The fromEmail field accepts any value via PUT /v1/sender-settings (no verification step). When this address is used in a send, Resend returns a 403 "domain not verified" response. The service logs the error, marks the message failed, and returns a 500 to the caller — but no user-facing explanation distinguishes "domain not verified" from "network error". Users who configure fromEmail and then see failed sends have no visibility into the actual cause.
B2 — No retry logic: transient Resend failures permanently fail the message
Severity: High
Location: src/index.ts → sendViaResend()
A single Resend API call attempt is made. Any network error, timeout, or Resend 5xx immediately marks the message status: "failed" with no retry. There is no queue, no exponential backoff, no dead-letter path. Transient failures (Resend degraded, Worker CPU timeout) are permanent from the caller's perspective.
B3 — Webhook replay creates duplicate messageEvents and double-counts engagement
Severity: Medium
Location: src/index.ts → POST /v1/webhooks/resend
Svix replays failed webhook deliveries automatically. Each replay results in a new row in comms_message_events and a second call to NEWSLETTER_SERVICE /internal/event. There is no deduplication on (message_id, status) before insert. An email that is "delivered" and then the webhook is replayed → newsletter sees two engagement events for the same send, inflating opens/clicks metrics.
B4 — messageEvents has no FK to messageLogs (documented as "not enforced")
Severity: Low
Location: src/db/schema.ts, migration 0000_warm_winter_soldier.sql
comms_message_events.message_id references comms_message_logs.id in intent but not via DB constraint. A webhook for an unknown or deleted providerMessageId inserts silently. No orphan cleanup, no alert. Over time the events table can accumulate orphan rows that degrade query performance and confuse log viewers.
B5 — SMS channel returns status: "sent" but never sends anything
Severity: Medium (feature is deceptive)
Location: src/index.ts → dispatchSend() SMS branch
When channel: "sms" is posted, the service logs to console and marks status: "sent". No Twilio call is made. The billing accumulator is NOT charged (accumulator only runs for email), but the call returns 200 OK. Callers (e.g., notification triggers from CI) believe the SMS was delivered. No error is surfaced.
B6 — senderEmail not validated as a syntactically valid email
Severity: Low
Location: src/index.ts → PUT /v1/sender-settings
The Zod schema for sender_email uses z.string().optional() — no .email() validator. A tenant can store "not an email at all" as their reply-to. Resend may silently strip or reject the reply_to header, causing customer replies to bounce.
B7 — DB handle cached by URL only — shared handle if two env vars collide
Severity: Low
Location: src/db/client.ts
The drizzle client cache key is COMMS_DATABASE_URL. If a misconfigured deployment sets two workers' COMMS_DATABASE_URL to the same string (e.g., a copy-paste error), they share a drizzle instance within the same Worker isolate. In tests, each test calls freshEnv() to bust the cache — production has no equivalent guard.
B8 — Alias check-then-claim is non-atomic; 409 on race not documented in dashboard UX
Severity: Low
Location: src/index.ts → GET /v1/aliases/availability, POST /v1/aliases/claim
The availability check and claim are two separate requests with a window between them. Another tenant can claim the slug after the check returns available: true but before the claim is submitted. The race is handled correctly at the DB level (unique constraint → 409 + credit refund), but the 409 is not documented as a possible response in the dashboard UI — users may see an unhandled error state.
Areas of Improvement
A1 — Rate limiting absent on /v1/send
A tenant can POST /v1/send in a tight loop, exhausting the Worker's Resend quota and incurring large credit bills (or hammering the billing accumulator). Native Cloudflare rate limiting (ratelimit binding) should be added with a per-tenant budget (e.g., 60 sends/min).
A2 — Implement exponential backoff + DLQ for Resend failures
Network errors and Resend 5xx responses should be retried with 1s/5s/15s delays via Cloudflare Queues. After 3 attempts, move to DLQ and mark the log status: "failed". This matches the pattern used in content-engine and newsletter services.
A3 — Domain verification flow missing
fromEmail is stored but there is no guided verification flow. The service should expose a POST /v1/sender-settings/verify-domain that calls the Resend domain verification API and surfaces the DNS records the tenant must add. Without this, fromEmail is a footgun.
A4 — Add messageEvents deduplication (idempotency on webhook)
Before inserting into comms_message_events, check if (message_id, status) already exists. Skip insert and newsletter fan-out if so. This makes webhook processing idempotent against Svix retries.
A5 — Add audit log for sensitive operations
Alias release and sender settings changes have no audit trail. A comms_audit_log table with (tenantId, action, actorId, payload, timestamp) rows would allow support to trace who changed what and when — especially important for "my alias was released and claimed by someone else" disputes.
A6 — Enrich /v1/logs with delivery event data
The current /v1/logs response shows the initial send status but not subsequent delivery events (delivered, bounced, opened). The UI has no way to show "email delivered at 10:05am". A JOIN or sub-select from comms_message_events would make the log view much more useful.
A7 — Implement Twilio for SMS
The SMS code path is a console stub. Either implement Twilio with the existing architecture (apiKey in env, same dispatchSend structure) or return 501 Not Implemented clearly so callers don't believe SMS was delivered.
Feature Completeness vs Spec
| Feature | Spec | Code Status |
|---|---|---|
| Email send (Resend) | ✅ | ✅ Implemented |
| Send idempotency | ✅ | ✅ Implemented |
| Sender settings + brand kit | ✅ | ✅ Implemented |
| Vlozi alias claim/release | ✅ | ✅ Implemented |
| Svix delivery webhooks | ✅ | ✅ Implemented |
| Newsletter fan-out on events | ✅ | ✅ Implemented |
| Message logs | ✅ | ✅ Implemented |
| Delivery analytics (/v1/stats, get_analytics) | — | ✅ Implemented (revamp P3) |
| Suppression list | — | ✅ Implemented (revamp P3, migration 0005) |
| Flows triggers + MCP tools | — | ✅ Implemented (Flows modernization) |
| SMS (Twilio) | ✅ | 🟡 Honest 501 (real Twilio deferred) |
| Multi-provider abstraction | ✅ (ADR) | ❌ Not implemented |
| Tenant BYOP (bring your own provider) | ✅ (adr-providers.md) | ❌ Not implemented |
| Provider management API (/v1/providers) | ✅ (api-spec.md) | ❌ Not implemented |
| Domain verification flow | ✅ (aws-ses-migration.md) | 🟡 Guardrail only (DOMAIN_NOT_VERIFIED); full self-serve deferred |
| Batch send endpoint | ✅ (api-spec.md) | ❌ Not implemented |
| Scheduled sends | ✅ (api-spec.md) | ❌ Not implemented |
| Retry with backoff | ✅ (domain-model.md) | 🟡 In-request retry ✅; queue+DLQ deferred (queue-limit) |
| Rate limiting | ✅ (scope-definition.md) | ✅ Implemented (native SEND_LIMITER) |