Architecture
Understand the two entry points, processing pipelines, error strategy, and source structure of Reverse Proxy for ntfy.
Overview
Reverse Proxy for ntfy has two entry points running on different runtimes:
- Cloudflare Worker — Handles incoming HTTP webhooks and email messages. Runs on Cloudflare's edge network.
- Node.js CLI — Manages the configuration file. Runs locally on your machine.
The worker and CLI are cleanly separated. The CLI writes config.json, and deploy bundles a copy into the worker, which checks it once when it starts instead of on every request and email.
Shipping the config inside the worker avoids environment variables, which Cloudflare caps at 5 KB each, so the number of contexts has no small fixed limit. A test config with 5,000 contexts built a worker of about 430 KiB gzipped that spent about 25 ms of CPU starting up on a laptop.
HTTP Pipeline
The worker processes HTTP requests through a linear 10-stage pipeline. Each stage transforms data and passes it to the next.
Failures before durable storage stop the pipeline and return an error response. A delivery failure after storage returns HTTP 202 and leaves the notification queued instead.
Stage Details
Receive — Extracts method, hostname, headers, and raw body from the incoming request. Redirects HTTP to HTTPS automatically (except on localhost).
- Rejects methods other than GET, POST, and PUT.
- GET requests skip the pipeline and return the landing page.
Route — Matches the request's subdomain against configured HTTP contexts. Resolves the context's server list and primary server.
- Returns 404 if no matching context is found.
- On
localhost, theX-NRP-Contextheader names the context instead (see Local Development).
Authenticate — Validates the Authorization header against the context's optional token. If the context has no token configured, all requests are allowed.
- Returns 403 on failure.
- Optionally forwards error details directly to the
error_topicif configured. This best-effort send bypasses the durable lane so its failure cannot block normal notifications.
Parse — Classifies the request body as JSON, text, or binary. Detects binary content via content-type headers or null byte scanning.
- Attempts JSON parsing if the content-type is
application/jsonor the body starts with{or[. - Returns 400 (
Unknown content type) for an empty body.
Interpret — Invokes the context's configured interpreter to transform the raw payload into a standardized notification object (title, body, priority, tags, icon, actions, etc.). HTML is automatically stripped from the notification body.
- Returns 422 if interpretation fails.
- Returns a null result if the interpreter intentionally ignores the payload, which produces an HTTP 200 with status
"ignored".
Format — Builds the final message body and ntfy headers (X-Title, X-Priority, X-Tags, X-Markdown, X-Icon, X-Actions, etc.). Appends visitor information (IP, location, ISP) if show_visitor_info is enabled.
Split — Splits messages exceeding 4000 UTF-8 bytes into numbered parts. Each part gets a title suffix like "(1/3)", "(2/3)", "(3/3)".
- Messages under the limit pass through as a single part.
Deliver — Saves a normal formatted notification and optional attachment in the SQLite-backed delivery outbox assigned to the context ID, then asks that lane to drain oldest-first.
- A failed head stays saved and blocks only that context's later notifications.
- The next notification for the same context triggers another attempt; there is no timer or manual retry action.
- Other context lanes continue independently. See Durable Delivery for the full behavior.
Send — Delivers notification parts to ntfy servers according to the delivery mode.
- In
send-oncemode, tries the primary server first, then fallbacks in order. - In
send-allmode, sends to all servers in parallel. - Handles attachments as a separate PUT request per server.
- Checkpoints successful message parts and attachments so retries skip confirmed work.
Respond — Builds the JSON response. Completed delivery returns HTTP 200. A notification that is safely saved but still waiting returns "queued" with HTTP 202.
- Includes detailed debug output if
show_response_outputis enabled.
Email Pipeline
The email pipeline processes messages from Cloudflare Email Routing through 9 stages.
Parse Email — Decodes the raw RFC 5322 email message. Extracts from, to, subject, and text body.
- Handles multipart/alternative MIME messages, preferring text/plain over HTML.
Route — Matches the recipient's local part (before the @) to a context ID.
Authenticate — Validates the sender address against the context's allowed_from field. Supports exact match and wildcard patterns (*@domain.com).
The remaining stages (Interpret, Format, Split, Deliver, Send) work identically to the HTTP pipeline.
Log — Outputs a debug JSON log with context name, interpreter, server results, body size, and part count to Cloudflare's real-time logs.
- Email processing is silent — there is no HTTP response to return.
- Email routing failures (e.g., no matching context ID for the recipient address) are silent — no error notification is sent to
error_topic.
CLI Architecture
The CLI reads and writes config.json through a config I/O layer. All config mutations go through schema validation.
- The generate command produces a
wrangler.tomlfile from the config and writes it next to yourconfig.json, plus the worker entry and a copy of the config in.wrangler/ntfy-reverse-proxy/beside it. - The deploy command chains validation, permission verification, KV provisioning, wrangler.toml generation, and deployment into a single step.
Error Strategy
| Layer | Strategy |
|---|---|
| Worker entry point | Try/catch around entire pipeline. Returns JSON with HTTP status. Includes debug details if show_response_output is on. |
| Pipeline stages | Each stage returns a typed result or throws. The entry point identifies which stage failed. |
| Interpreters | Manual payload extraction. The ntfy-json interpreter and the transmission-vpn, pfsense-actions, discord-stonker-bot, and outpost interpreters use strict schema validation; all others use lenient field detection. Malformed JSON and schema failures throw. The entry point can forward the failure directly to error_topic if configured. |
| Delivery outbox | Persists before sending. Keeps a failed head in its context lane and retries it when the next notification for that context arrives. |
| Error topics | Sends error-debug.json directly as a one-shot diagnostic. A failed diagnostic is not retried and never enters or blocks the durable delivery lane. |
| Send/fallback | Never throws. Collects per-server results. Primary failure triggers fallback. Returns success/failure per server. |
| CLI | Try/catch at command level. Chalk-formatted errors. Non-zero exit code on failure. |
| Config validation | Schema validation on load. Checks orphaned server refs, duplicate context IDs, missing fields, invalid token format. |
Source Structure
src/
worker/
index.ts — Worker entry point (fetch + email handlers)
handlers.ts — HTTP and email pipeline orchestration
durable-objects/
delivery-outbox.ts — Per-context SQLite FIFO lane
pipeline/
receive.ts — Extract request metadata
route.ts — Match subdomain/email to context
authenticate.ts — Validate tokens and sender addresses
parse.ts — Detect body type (text, JSON, binary)
interpret.ts — Dispatcher to interpreter modules
accumulate.ts — KV state management for Statuspage
format.ts — Build message body and ntfy headers
split.ts — Smart split for large messages
email.ts — RFC 5322 email parser
deliver.ts — Persist and trigger the context lane
drain-fifo.ts — Oldest-first drain behavior
send.ts — Deliver to servers with fallback
respond.ts — Build HTTP response
interpreters/
plain-text.ts — Pass-through text or binary attachment
ntfy-json.ts — ntfy JSON mapping
synology.ts — Synology DSM webhooks
statuspage.ts — Statuspage.io webhooks
seerr.ts — Seerr media requests
pfsense.ts — pfSense email notifications
unifi.ts — UniFi email notifications
app-payload.ts — Shared reader for app alert payloads
transmission-vpn.ts — Transmission VPN alerts
pfsense-actions.ts — pfSense Actions alerts
discord-stonker-bot.ts — Discord Stonker Bot alerts
outpost.ts — Outpost alerts
landing/
page.ts — Landing page HTML
cli/
index.ts — CLI entry point
commands/
server.ts — Server CRUD
context.ts — Context CRUD
settings.ts — Settings management
config-io.ts — Config file read/write
generate.ts — wrangler.toml generation
validate.ts — Config validation
deploy.ts — Cloudflare deployment
menu/
interactive.ts — Interactive TUI
lib/
item.ts — App name constant
package-runtime.ts — Packaged CLI paths and version
regex.ts — Shared regex patterns
schema.ts — Zod schemas
utility.ts — Helper functions (HTML stripping)
tests/ — Unit and integration tests (mirrors src/)
types/ — TypeScript type definitions (mirrors src/)