Skip to main content

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.

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_topic if 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/json or 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-once mode, tries the primary server first, then fallbacks in order.
  • In send-all mode, 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_output is 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.toml file from the config and writes it next to your config.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

LayerStrategy
Worker entry pointTry/catch around entire pipeline. Returns JSON with HTTP status. Includes debug details if show_response_output is on.
Pipeline stagesEach stage returns a typed result or throws. The entry point identifies which stage failed.
InterpretersManual 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 outboxPersists before sending. Keeps a failed head in its context lane and retries it when the next notification for that context arrives.
Error topicsSends 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/fallbackNever throws. Collects per-server results. Primary failure triggers fallback. Returns success/failure per server.
CLITry/catch at command level. Chalk-formatted errors. Non-zero exit code on failure.
Config validationSchema validation on load. Checks orphaned server refs, duplicate context IDs, missing fields, invalid token format.

Source Structure

text
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/)