-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
Stateless. No database. Logs to stdout. Trivially deployable.
graph TD
EXT[External Service<br/>Stripe / GitHub / Cal.com] -->|POST JSON| API[Express /hooks/:source]
API -->|optional HMAC check| SIG{Signature ok?}
SIG -->|no| REJ[401]
SIG -->|yes| TPL{Template exists?}
TPL -->|src/templates/source.js| FMT[Custom formatter]
TPL -->|no template| DEF[Default formatter]
FMT --> EM
DEF --> EM[Resend send email]
EM --> SL{Slack enabled?}
SL -->|yes| SLACK[POST to Slack webhook]
SL -->|no| END[Return 200]
SLACK --> END
classDef ext fill:#a78bfa,stroke:#a78bfa,color:#fff
class EXT,EM,SLACK ext
sequenceDiagram
participant Ext as External Service
participant API as /hooks/source
participant V as HMAC verify
participant T as Template
participant R as Resend
participant S as Slack
Ext->>API: POST /hooks/stripe with body + X-Signature
API->>V: verify(body, secret, signature)
V-->>API: ok
API->>T: format(payload)
T-->>API: { subject, text, html }
API->>R: send email
R-->>API: 200
alt Slack enabled
API->>S: POST formatted message
end
API-->>Ext: 200 { ok: true }
| File | Responsibility |
|---|---|
src/index.js |
Express app, routing, signature verification, retry orchestration |
src/templates/<source>.js |
Per-source formatters (Stripe, GitHub, Cal.com, etc.) |
Dockerfile |
Alpine Node 20 image, ~80MB |
docker-compose.yml |
One-command deploy with health check |
Stateless by design. No database means no migrations, no connection pool, no failures from database outages. If you need a queue, put one in front of this service.
Single retry on 5xx. Resend has occasional transient 5xx errors. One retry with 500ms backoff catches almost all of them. More retries belong in a queue.
Per-source templates. Webhook payloads vary wildly. Stripe's invoice.paid is nothing like GitHub's push. A template per source keeps formatting logic isolated and easy to add new sources.
Default formatter (pretty JSON). When no template exists, just send the JSON as a code block. Means you can plug a brand new webhook in with zero config and start receiving emails.
HMAC verify is optional. For internal sources (your own cron jobs), you don't need it. For Stripe / GitHub, you must. The header parsing handles sha256=<hex>, <hex>, X-Signature, X-Hub-Signature-256, X-Stripe-Signature.
Single-process Node. ~50MB RSS at idle. ~80MB under load (10 req/sec sustained).
Handles ~1000 req/sec on a single CPU core before Resend rate-limits become the bottleneck. The bottleneck is always Resend, not this service.