Documentation

Architecture

How events flow from your app through queues to channel delivery.

Overview

Payghaam splits into an HTTP API, background workers, PostgreSQL (source of truth), and Redis (queues + cache). The dashboard is a Next.js app for operators; the Flutter SDK and REST API are integration surfaces for your product.

Data flow
SDK / server / webhook ─▶ API edge (auth + identity verification)
        │ buffered — request returns immediately ("accepted")
        ▼
   Redis / BullMQ ─┬─▶ event-ingestion-{0..N}  (FIFO per user)
                   │        └─ Ingestion worker: upsert subscriber, persist event
                   │             └─▶ journey-user-{0..N}  (FIFO per user)
                   │                    └─ JourneyEngine: conditions, actions, timers
                   └─▶ delivery               (high concurrency)
                          └─ ChannelStrategyFactory → APNs / FCM / SMS / email
                               └─ classify failures → ProjectIssue (notifications)

Ingestion buffer

High-frequency writes from the SDK — identify, subscription registration, tag updates, events, and delivery receipts — never touch Postgres on the request. The edge validates the call (including identity verification), pushes it onto a buffer, and returns accepted. A worker then resolves the subscriber, persists the row, and routes journey events downstream. A Postgres hiccup degrades to queued, not a 5xx.

The buffer is partitioned by external_id (FIFO, one consumer per partition), so a single user's mutations are applied in order, and every job carries an idempotency key with a durable completion marker — so a client retry is exactly-once. Low-frequency calls that need a definitive answer (consent opt-out, API journey triggers) stay synchronous.

Per-user ordering

Both the ingestion buffer and the journey engine hash a user into one of N FIFO partitions, so step order is preserved end to end even under load. Delivery workers run at higher concurrency because network I/O is the bottleneck.

Journey graph

  • Nodes are actions (send push, wait, branch).
  • Edges carry conditions (tag filters, event properties) — conditions live on edges, not nodes.
  • Wait steps schedule delayed jobs; stale timers are ignored if the user already moved on.
  • Idempotency keys prevent duplicate sends on worker retries.

Delivery gateway

Each send creates a MessageDelivery row. The worker picks a channel strategy (FCM, APNs, Twilio, SMTP, webhook), applies consent and frequency caps, and sends through a per-channel circuit breaker. Failures are normalized across providers into a category (config / token / rate / provider-down / payload) and stored with the row.

Channel-wide problems and provider outages roll up into deduplicated issues that surface in the dashboard notification manager, and auto-resolve when sends recover. See Delivery health.

Secrets at rest

FCM service accounts, APNs .p8 keys, and SMTP/Twilio credentials are encrypted with AES-256-GCM before storage. The dashboard never returns raw secrets — only a configured indicator.