Skip to content

ZevSend

1. Snapshot

Product ZevSend
What it is Transactional messaging API for developers — email today, SMS / WhatsApp / Verify as additional channels
Public URL console.zevsend.com (customer dashboard); api.zevsend.com (developer API)
Status Live (email) · SMS + WhatsApp + Verify gated on admin-side carrier enablement
Product owner Daniel Arowolo
Parent company ZevOP Technologies Limited

2. Mission

ZevSend is the transactional-messaging backbone of the Zev ecosystem. A developer writes one integration — POST /v1/emails for receipts, POST /v1/sms for shipping updates, POST /v1/verify for OTPs — and ZevSend handles domain verification, sender-brand approval, delivery, bounce + complaint feedback, suppressions, retries, and per-team usage accounting. Billing is in Naira through ZevPay; identity comes from ZevID; the same team-and-domain model that customers already know from ZevCloud applies here without re-learning a new ownership concept.

The reason ZevSend exists: every credible transactional-messaging API today (Postmark, Resend, SendGrid, Twilio) is operated abroad, USD-billed, and tuned for a global audience that doesn't include Lagos. A Nigerian SaaS team that wants reliable transactional email pays in dollars, exposes itself to FX volatility, and accepts that delivery-feedback dashboards, brand-approval queues, and support correspondence all run on someone else's timezone. ZevSend is built to remove that friction for the Nigerian and West-African developer market — same engineering rigour, Naira-billed, locally operated, and stitched into the rest of the Zev platform so a domain bought at ZevCloud already verifies on ZevSend with one click.

That positioning is structural, not just a billing label. ZevSend is the first Nigerian-operated transactional-messaging platform that ships a real brand-identity gate, per-domain display-name registration, real-time delivery + bounce + complaint webhooks, and a credit-funded usage model — in one platform, in Naira, for one account that already works across ZevID, ZevPay, ZevCloud, and the rest of the Zev surface.

ZevSend is deliberately transactional only. Receipts, password resets, shipping notifications, OTPs, account alerts — the kind of mail recipients are expecting because they did something. Bulk marketing — newsletters, broadcasts, drips, lead nurture — is the role of ZevCampaign, a sibling product on the same platform built for marketers. The split is not a marketing decision; it is a deliverability decision. Transactional and marketing email have fundamentally different complaint-rate profiles, and mixing them on the same sending reputation degrades both. Every product-level control on ZevSend (brand-identity admin approval, per-team suppression list, anti-spammer policies, the absence of a bulk-list feature, abuse-driven suspension) exists to keep ZevSend's reputation tuned for transactional. A customer who attempts to use ZevSend for marketing is told, in product, to use ZevCampaign instead.

3. Audience

Users of ZevSend are people who write code and need to send mail (or, soon, SMS / WhatsApp / OTPs) from inside their applications. In practice today, that means:

  • Solo developers and small SaaS teams shipping signup flows, password resets, receipts, and shipping notifications without the overhead of a global provider billed in USD.
  • Marketplaces and platforms that need a reliable transactional channel separate from any future marketing channel — receipts must land, period.
  • Nigerian companies migrating off self-hosted SMTP — they already have a domain and want managed deliverability without standing up Postfix and warming IPs themselves.
  • Teams building on top of the Zev ecosystem — a customer who is already on ZevCloud for hosting + ZevPay for payments adds ZevSend as their transactional sender without onboarding a fourth vendor.

A ZevSend account is always a team — even a solo user has a team-of-one. Teams own domains, API keys, templates, suppressions, and invoices; team members are invited with scoped access. A single ZevID account can belong to many ZevSend teams. Switching teams in the dashboard switches every domain, key, send-log, and credit view to that team's scope.

4. Core capabilities

Each capability below is something a developer or a team-admin can use from console.zevsend.com or against api.zevsend.com today.

Domain verification + brand identity

A team adds a domain they own; ZevSend registers it on the active sending carrier (AWS SES today, with the carrier layer abstracted so future providers slot in), publishes the exact DNS records the team needs to add (DKIM, custom MAIL FROM, SPF, DMARC), and runs a continuous health check via DomainHealthService — verified domains drop to temporary_failure if their records ever stop resolving and to failed after a 48-hour grace window, surfacing a banner in the dashboard the moment the customer can act on it.

Once DNS is verified, the team submits brand identity — brand name, legal entity, support email, optional support URL. These fields are server-injected at send time as {{brand.*}} template variables and cannot be overridden through the API; a leaked key cannot send mail as a different brand name on a verified domain. An admin reviews each submission and the team's first approved domain auto-promotes the team from sandbox to live. The two-step sandbox-vs-live gate (verified DNS plus admin brand approval) is what stops the platform from being abused as a generic open relay by sign-up traffic.

Additional display names per domain

A team that needs a second brand string on the same domain (e.g. "Acme Receipts" alongside the primary "Acme") registers it from the domain detail drawer. Each registered display name gets a stable dn_xxxxxxxx public id; once admin-approved, it's usable on the API as either a registered alias by ID (from_display_id: "dn_...", recommended) or as the display portion of an RFC 5322 from value ("Acme Receipts" <support@acme.com>). Sends that pass any unregistered display string are rejected at submit. Domain functioning is not affected while a display-name application is in review; the primary brand keeps working.

Direct + template send modes

The send API has two shapes:

  • Direct mode — the customer supplies from, to, subject, and html / text. The customer owns the body. This is the path most teams take during migration from Resend / Postmark / SendGrid / SES.
  • Template mode — the customer references a template_id and passes customer.* variables; ZevSend renders the body server-side from a platform-managed or team-owned template. brand.* variables are server-injected from the domain's approved identity and cannot be overridden by the per-send payload.

Both modes go through identical validation: from-domain ownership + verification + approval, suppression-list check, API-key scope enforcement, and (for SMS / WhatsApp / Verify) per-channel plan-feature flags.

SMS + WhatsApp + Verify

The same team / domain / brand model extends to non-email channels:

  • SMS sends route through admin-enabled SMS carriers (per-country routing planned). SMS templates (sms_order_confirmation, sms_delivery_update, sms_appointment_reminder) ship seeded. Per-send pricing is charged against the team's Zev Credit balance.
  • WhatsApp rides Meta's Cloud API today. WhatsApp templates require Meta-side approval per Meta's policy; ZevSend stores the approved template name / language / category on the row and dispatches against them.
  • Verify is a server-rendered OTP channel — POST /v1/verify { channel, to } generates a code, dispatches via the requested channel, returns a verification_id; the customer later calls POST /v1/verify/:id/check { code }. The customer never writes the body; ZevSend renders the channel-appropriate text. Per-channel verify pricing is admin-tuned and surfaced to the customer on the Credits page.

Sandbox teams can exercise every channel for testing, but only against verified recipient addresses (added explicitly per-team). Going live unlocks unrestricted recipient sends; the gate is the same approved-domain check.

Custom sender ID applications (SMS)

A team that wants a registered SMS sender ID applies through the Settings page — value, justification, target country codes. Admin reviews and either creates the resulting sms_sender_ids row on approval or rejects with a reason. Until approved, sends fall back to the platform's default carrier-issued sender on that route.

Suppression list

Every team has a suppression list keyed on recipient address. The list is populated automatically when the carrier reports a hard bounce or complaint; sending to a suppressed address is rejected before the carrier is ever asked. Admins (team-side) can view and remove suppressions from the dashboard.

Webhook events

Every transactional event (email.sent, email.delivered, email.bounced, email.complained, email.failed, similarly for SMS / WhatsApp / Verify) is dispatched as an outbound webhook to whatever endpoint the team has configured. Deliveries run through a BullMQ queue with five retry attempts and exponential backoff. The dispatch happens after the customer's send response has already returned, so a slow webhook endpoint never slows a send.

Email + credit usage metering

Every send increments the team's per-channel usage counters. For email, the team has a monthly plan quota (e.g. 3k on Free, larger on paid tiers); sends past the quota are charged against Zev Credit at the per-template price. For SMS / WhatsApp / Verify, every successful dispatch debits Zev Credit at the per-channel price. The customer sees both the remaining-quota figure and the per-call cost on the Credits page in the dashboard before they integrate.

API keys, env separation, and domain-scoping

Each team mints API keys for programmatic use. Two strict rules apply:

  • Env separation — keys are either sk_test_* or sk_live_*. Test keys can only be created from the setup wizard's "bootstrap" path while the team has no active keys; live keys are minted only from the standalone /api-keys page and require the team to have at least one admin-approved domain. The two paths cannot produce the wrong env, even with a tampered client.
  • Domain scoping — a team with multiple verified domains can pin a key to one of them. Sends from that key are refused if the from address resolves to a different domain. This caps the blast radius of a leaked key in multi-brand teams.

Plaintext is shown once at creation; only the SHA-256 hash is stored.

Team-based access + audit

Teams have an owner plus member roles. Every admin action against a customer resource (domain approval, display-name approval, sender-id review, plan change, suspend / reinstate) writes a row to the append-only admin_audit_logs table. Permission splits exist precisely so an ops teammate handling brand reviews doesn't automatically get billing-mark-paid rights — those are separate decorators on separate endpoints.

Naira billing through ZevPay

Subscriptions, plan upgrades / downgrades, scheduled-downgrade handling, and credit top-ups all run through ZevPay. The customer's payment methods live at ZevPay and are reused across products. Refunds can route either to the original payment method or to the team's Zev Credit balance.

Zev Credit (credit-as-currency in the Zev ecosystem)

ZevSend participates in the same central credit ledger ZevID owns. See the canonical model at Cross-product → Zev Credit. ZevSend-specific behaviour:

  • Accepts credit on every paid invoice automatically (plan subscriptions, plan upgrade pro-rations).
  • Drains credit on per-send usage for SMS / WhatsApp / Verify, and for email sends past the plan quota.
  • Issues credit on refunds (source: refund) when an admin chooses "refund to credit" instead of "refund to original method".
  • Universal scope — credit ZevSend issues is spendable across the ecosystem (not locked to ZevSend), and credit issued by other Zev products is spendable on ZevSend usage and subscriptions.
  • Per-team holder — Zev Credit earned through ZevSend is held by the team, not the individual user, and travels with the team on ownership transfer.

5. Architecture summary

  • Backend — a NestJS API serving the customer GraphQL surface (/graphql), the public REST API (/v1/*), internal ZPIP endpoints, and admin GraphQL. Hosted on Coolify-managed containers.
  • Database — a managed Neon Postgres instance holding teams, domains, display-name applications, sender-id applications, templates, send logs (per channel), suppressions, webhooks, API keys, invoices, subscriptions, audit log, and the verification table.
  • Cache + queue — Redis (BullMQ) for webhook delivery, scheduled retries, and rate-limit storage.
  • Outbound email carrier — AWS SES with a per-product configuration set (zevsend-default) and IAM credentials scoped to it. Bounce / complaint / delivery events route via SNS → /webhooks/aws-sns.
  • Outbound SMS carriers — admin-configurable; first-class adapter for African-region carriers via the sms-carrier-router.
  • Outbound WhatsApp carriers — Meta Cloud API today; provider abstraction allows additional WABA providers.
  • Authoritative DNS healthDomainHealthService cron re-queries SES per verified / pending / temporary-failure domain every 5 minutes, applying a 48-hour grace before escalating a verified row to failed.
  • Edge protection — every public surface sits behind a CDN with TLS termination, HSTS, and rate limiting.
  • Centralised exception + log monitoring — every error and important security event is forwarded to a monitoring platform the engineering team watches.
flowchart LR
    Dev([Developer]) -->|TLS| Edge[Edge / CDN]
    Edge --> API[ZevSend API]
    API --> DB[(Platform database)]
    API --> Cache[(Redis / BullMQ)]
    API --> SES[AWS SES]
    API --> SMS[SMS carriers]
    API --> WA[WhatsApp carriers]
    SES -->|delivery / bounce / complaint| SNS[AWS SNS]
    SNS --> API
    API --> WebhookOut[Outbound webhook delivery]
    ZevID -->|identity| API
    API -->|billing| ZevPay
    Other[Other Zev products] -->|ZPIP| API

6. Key flows the user sees

Sign up → first verified domain → first live send

  1. Developer signs in to console.zevsend.com with their ZevID account.
  2. The setup wizard creates a default team and surfaces three steps: generate API key, send a test email, add a domain.
  3. The "Generate API key" button mints a sk_test_* key with source: ONBOARDING (this single path is the only way to get a test key without an approved domain).
  4. The developer adds a domain (typed in or picked from ZevCloud via the ZPIP picker). ZevSend registers it on SES, returns the DKIM + custom-MAIL-FROM + SPF + DMARC records the team needs to publish.
  5. DomainHealthService polls SES every 5 minutes; the domain auto-promotes to verified once DKIM is observed.
  6. The team submits brand identity. An admin approves; the team auto-flips from sandbox to live, and the team can now mint sk_live_* keys from the standalone /api-keys page.
  7. First live send goes out. The carrier accepts → email.sent webhook fires immediately; SES emits delivered over SNS within seconds, the row transitions, email.delivered webhook fires.

Display-name registration

  1. Team opens an approved domain on the Domains page → Additional display namesAdd display name.
  2. Submits the string + optional description / context for the reviewer. Row gets a dn_xxxxxxxx public id immediately, status pending.
  3. Admin reviews on the admin-side display-names queue. On approval, status flips to approved and the dashboard exposes the public id with a copy-button so the customer can paste it into their integration.
  4. Customer references the id at send time via from_display_id. Resolution is exact (no string-match ambiguity), the send composes the wire-format From: header with the canonical display string, and the recipient sees the registered brand name.

Verify OTP

  1. Customer's app calls POST /v1/verify { channel: "sms", to: "+234..." }.
  2. ZevSend generates a 6-digit code, persists the hash + expiry, debits Zev Credit at the platform-set per-call rate (currency-aware), dispatches the SMS via the configured carrier.
  3. Customer's app shows the OTP-entry screen to the end user.
  4. End user submits the code → customer's app calls POST /v1/verify/:id/check { code }. ZevSend compares hashes, marks the verification record verified (or failed on mismatch), returns the outcome.

Subscription renewal

  1. BillingRenewalService runs daily at 2am Africa/Lagos, picks up subscriptions whose next-renewal date is within the lookahead window.
  2. For each due subscription, the service issues an invoice through ZevPay or, when the team has scheduled a downgrade, switches the team's plan in place (if the target plan is free, no invoice is created).
  3. Customer pays through their ZevPay-stored payment method or via Zev Credit; on success, the team's plan renews for the next billing period.

7. Data the product handles

Category Examples
Account pointer accountId issued by ZevID — actual identity (email, name, MFA) lives in ZevID, not here
Teams + memberships Team name, owner, member roles, invitation records
Domains Domain name, verification + admin-review state, brand identity (brand name, legal entity, support email + URL), DNS records the customer should publish, SES carrier id, health-check counters
Display-name applications Customer-registered alternate brand strings per domain — submitted display string, normalised form, justification, review status
Sender-ID applications (SMS) Customer-submitted SMS sender id requests + admin review state
API keys One-way hashes of programmatic keys, env (test / live), type (secret / publishable), optional domain scope
Templates Platform-managed + team-managed message templates per channel, with variables declared as customer.* or brand.*
Send logs Per-channel message rows — recipient, sender, subject (email), template id, status, carrier message id, sent / delivered / failed timestamps
Verifications OTP records — hashed code, expiry, channel, recipient, outcome
Suppressions Per-team list of recipients to refuse sends to, with reason (hard_bounce, complaint, manual)
Webhooks Per-team subscription rows (URL, events, secret) + a BullMQ-backed delivery audit
Billing artefacts Invoices, subscriptions, scheduled-plan-changes (no card numbers — ZevPay holds the tokens)
Audit log Admin actions against customer resources (domain approve / reject, display-name approve / reject, plan change, suspend / reinstate)

For the full data inventory with field-level encryption notes and retention specifics, see compliance docs → ZevSend data inventory.

8. Security posture

Area Posture
API keys Stored as SHA-256 hashes; plaintext shown once at creation and never persisted; env separation enforced server-side so /api-keys cannot mint test keys and the wizard cannot mint live keys
Domain brand identity Locked at admin approval; cannot be overridden by per-send API payload ({{brand.*}} is server-injected)
Display names Pre-approval-gated; sends with unregistered display strings are rejected before the carrier is ever called
API rate limiting Global ThrottlerGuard wired as APP_GUARD; public REST endpoints carry per-route @Throttle caps (60 req / 10s on /v1/emails, /v1/sms, /v1/whatsapp; 20 req / 10s on /v1/verify — tighter cap against OTP enumeration / SIM-pumping)
Database-level encryption Provider-managed at-rest encryption on Neon
Encryption in transit TLS 1.2+ everywhere; HSTS on user-facing surfaces
Authentication (customer) ZevID OAuth — ZevSend never holds passwords
Authentication (admin) ZevID OAuth + server-side RBAC permission list; every admin endpoint declares its required permission via @RequireAdminPermission(...); MFA-gated mutations (plan change, invoice mark-paid) additionally require x-admin-mfa-token
Tenant isolation Customer-resource rows scoped by team_id at the query layer; cross-team reads / writes blocked in middleware before the controller runs
Webhook outbound Dispatched off the request path (setImmediate) so a slow customer endpoint never slows a send; 5 retry attempts with exponential backoff; deliveries audit-logged
Webhook inbound (SNS) AWS SNS signature verification on /webhooks/aws-sns (uses the existing SnsSignatureVerifier)
Cron concurrency Single-replica in-flight guard on DomainHealthService, VerifyJanitorService, BillingRenewalService — a slow sweep can't overlap the next tick
GraphQL hardening Inline depthLimitRule(10) validation rejects deeply-nested queries before the resolvers run
Configuration-set isolation SES zevsend-default configuration set carries the per-product reputation + event-destination setup, with team-id tags so feedback events route deterministically
Logging + monitoring Centralised exception + log monitoring; every admin action against a customer resource produces an audit-log row; PII (recipient email, customer name) is intentionally kept out of error logs
Backup Continuous point-in-time recovery on Neon
Vulnerability management Automated dependency-advisory scanning

For the full security record, see compliance docs → ZevSend security.

9. Compliance posture

  • Nigeria Data Protection Act (NDPA), 2023 — primary obligation. ZevSend is the controller for its customer accounts, team members, billing contacts, domain registrants, and the metadata of sends ordered through the platform. For the content customer applications dispatch through ZevSend — the recipient addresses, the message bodies — ZevSend is a processor; the deploying customer is the controller and decides who to message. The distinction matters when an end-recipient sends a subject-rights enquiry: it's routed to the deploying customer, not handled by ZevSend directly. Suppressions and bounce / complaint feedback that ZevSend records are operational metadata, retained per-team.
  • NDPC General Application and Implementation Directive (GAID), 2025 — implementing rules including 72-hour breach notification, DPO designation, RoPA. DPO designated: Izunna Ikewete (dpo@zevop.com).
  • CAN-SPAM, CASL, and analogous anti-spam regimes — ZevSend's brand-approval gate, server-injected sender identity, and per-team suppression list are the operational controls that keep platform sends within legitimate transactional bounds. Marketing-email sends are explicitly not the role of ZevSend (ZevCampaign is the future product for that); a customer who attempts to use ZevSend for bulk marketing has their team subject to suspension under abuse policy.
  • Cross-border transfer posture — primary data store is in the United States (Neon-on-AWS). SES processes mail through AWS regions (default region: eu-west-1). Sub-processor detail and DPA records are maintained at compliance.zevop.com.

For the detailed compliance record (RoPA, third-party DPAs, retention table, subject-rights procedure, breach-response runbook), see compliance docs → ZevSend.

10. Integrations

Zev products this depends on

  • ZevID — every authentication call, every cross-product permission check, every enrollment write goes through ZevID. ZevSend holds no identity data of its own; it addresses every user by the accountId ZevID issues.
  • ZevPay — every invoice, subscription, and refund routes through ZevPay; payment-method tokens are minted at ZevPay, never at ZevSend.
  • ZevCloud — via ZPIP, ZevSend asks ZevCloud to publish the DKIM / SPF / DMARC / bounce-routing DNS records on a domain whose authoritative DNS lives at ZevCloud. The customer grants the scope through a ZevID consent screen; the token carries their accountId so ZevCloud can verify domain ownership before writing.

Zev products that depend on ZevSend

  • Any Zev product that needs to send transactional mail / SMS / OTP to its own users (today, ZevID's signup OTP flow; future products as they ship). The relationship is the same shape as every other ZPIP integration: the consuming product calls ZevSend with a service+user token, ZevSend renders + dispatches, and feedback events fan out through ZevSend's standard webhook surface.

11. Roadmap signals

  • This quarter — Customer-facing display-name publicId copy chip on the Domains drawer (shipped); from_display_id API field (shipped); cumulative-sent counter semantics on the Overview dashboard (shipped).
  • This quarter — Customer SMS templates seeded for the catalogue (sms_order_confirmation, sms_delivery_update, sms_appointment_reminder — shipped); WhatsApp customer-facing templates deferred until WABA-side approval flow is built.
  • This year — Inbound DMARC report parser as a backend module, ingesting via SES Inbound (planned; see internal docs/dmarc-analyzer-future.md).
  • This year — Per-domain deliverability dashboard on the customer console: source-IP table, disposition history, pass-rate sparkline.
  • This year — SES dedicated-IP pool for transactional once marketing volume on a sibling product (ZevCampaign) approaches the same SES account.

12. Contacts

  • Product owner: Daniel Arowolo — (email)
  • Security: security@zevop.com
  • DPO: Izunna Ikewete — dpo@zevop.com

13. Change history

  • 2026-05-29 — Initial profile published. Captures: SES-backed email send pipeline with custom MAIL FROM + per-domain brand identity + admin approval gate, display-name registration with from_display_id lookup, SMS / WhatsApp / Verify channels riding the same team / domain / brand model, env-separated API keys with onboarding-vs-console source enforcement, suppression list driven by carrier bounce / complaint feedback, outbound webhook fan-out via BullMQ off the request path, ZPIP integration with ZevCloud for DNS provisioning, Zev Credit participation (issues on refund, accepts on every paid invoice, drains on per-send usage).