Get started
How it works
A 2-minute mental model of the platform. Read this once and the rest of the docs make sense.
The loop
Every message Nudgel sends follows the same loop, whether you kicked it off from a curl call or from the dashboard:
- You hit
POST /v1/messageswith a channel, recipient, template, and variables. - The API authenticates the request (API key or sign-in session), validates payload, persists a row in
messages, and enqueues a job in BullMQ. - A worker picks it up, asks the
CredentialResolverfor the right provider client (per your routing rules), and calls the provider. - Provider responds. Worker writes
sentto the message row. Webhooks from the provider later mark itdelivered,bounced, etc. - You see the timeline in the dashboard, get notified via your own webhook, and the audit log records who triggered it.
HONEST STATUS
/v1/messages endpoint.Tenants
A tenant is the unit of isolation. Everything in Nudgel belongs to a tenant: contacts, templates, providers, API keys, audit log, delivery history. Per-tenant Postgres row-level security plus an application-layer guard means one tenant can't see another's data, period.
Most teams use a single tenant. Some run separate tenants per brand (one for the consumer app, one for the operations dashboard) or per environment (production vs staging vs dev). You can create unlimited tenants on every plan.
Channels
Four channels, all hit through the same messages.send call — you change the channel field, the rest stays roughly the same:
| Channel | What it sends | Live now providers |
|---|---|---|
sms | Text up to 1600 chars (segmented) | AT, Twilio, Daraja |
email | HTML + plain text | Postmark, Resend, SES |
push | App push notifications | FCM, APNS |
whatsapp | Templates + session messages | Meta WABA (beta) |
Providers and routing
You bring your own provider keys. We don't mark up sends — your Africa's Talking bill comes straight from AT, your Twilio bill from Twilio. Connect each provider once, then define routing rules:
channel: sms
rules:
- priority: 50
match: { country: KE, segment: txn }
primary: at-ke-shortcode
fallback: [at-ke-bulk, twilio-global]
- priority: 100
match: { country: KE }
primary: at-ke-bulk
- priority: 100
match: { country: NG }
primary: twilio-ng
- priority: 900
match: { } # any
primary: twilio-globalRules evaluate top-down. First match wins. If the primary fails transiently, we retry the fallbacks in order. Permanent failures (bad number, suppressed) terminate as expected.
Templates
A template is a named, versioned message body with variable substitution. SMS uses plain text; email uses MJML; WhatsApp uses Meta-approved template format; push uses a JSON payload schema.
Variables are {{like.this}} in Liquid. Templates can render in different languages — pass locale: "sw"in the send call, we pick the Swahili variant if you've uploaded one. Every template is versioned; you can roll back to any prior version.
Isolation and encryption
Three layers protect your data:
- App layer — every repository method takes
tenantIdas a required first argument. A custom ESLint rule blocks DB queries that don't scope by tenant. - DB layer — Postgres row-level security on every tenant table. The session sets
SET LOCAL app.tenant_id; policies match against it. Forgot to scope? You get zero rows. - KMS — provider credentials are encrypted with a per-tenant master key (which is itself encrypted with a root key on the host). The
CredentialResolverservice is the only code path allowed to decrypt — enforced by ESLint.
Who runs what
| Concern | Owner |
|---|---|
| Sign-in / OTP / Google OAuth | our auth service |
| Tenant + membership + API keys | Nudgel |
| Provider credential storage | Nudgel (envelope encrypted) |
| Sending (queue, worker, provider call) | Nudgel workers |
| Provider rates + invoicing | Your provider directly |
| Hosted dashboard | Nudgel (Next.js) |
Auth is delegated to our auth service so the human-side identity stays the same across every AutoTribes product (Nudgel, TribeFest and InstaEscrow, etc.). Tenancy and sending are Nudgel's exclusive domain.