Skip to content

Architecture and How Delivery Works

todoke is a Web Push notification SaaS built entirely on the Cloudflare platform. This page explains the internal flow from an API call to the notification reaching the browser, along with the mechanisms that keep delivery stable.

todoke is composed of the following Cloudflare components.

ComponentRole
Cloudflare WorkersRuntime for the REST API and Queue Consumer
Cloudflare D1Persistence for apps, subscribers, API keys, delivery logs, and more (SQLite)
Cloudflare KVTemporary storage for sessions and OAuth state
Cloudflare QueuesBuffering and asynchronous delivery of notifications
Cloudflare PagesHosting for the dashboard (SPA)

Delivery is automatically routed to the appropriate external Web Push service (Google FCM, Mozilla autopush, Apple’s APNs for Web Push, etc.) depending on the subscriber’s browser and OS. This is determined by information contained in the endpoint of the browser-side Push Subscription, and is not something todoke selects explicitly.

Notification delivery follows this path.

REST API → Cloudflare Queues → Queue Consumer Worker → Push service (FCM, etc.) → browser
  1. A client (SDK, CLI, curl, etc.) calls POST /api/v1/notify
  2. The API validates the payload size, plan limits, and so on, then enqueues the notification into Cloudflare Queues
  3. The API returns 202 Accepted as soon as the enqueue completes
  4. The Queue Consumer Worker asynchronously pulls the message and delivers it to the Push service for each subscriber

This asynchronous design means that even apps with many subscribers can always return a fast response without the API request timing out.

If processing in the Queue Consumer Worker fails, the message is automatically retried.

  • Retry count: up to 3 times per message
  • When retries are exhausted: the message is sent to the DLQ (Dead Letter Queue)
  • Messages that reach the DLQ: their content is recorded and then discarded. In other words, that notification is not resent and is lost

A browser subscription expires when, for example, the user stops allowing notifications or the subscription period lapses on the browser side. When todoke receives an HTTP 410 (Gone) or 404 (Not Found) during delivery to the Push service, it automatically deactivates that subscription at that point.

A deactivated subscription is treated as follows.

  • Excluded from the targets of subsequent notification sends
  • Not counted toward the subscriber count (the count subject to plan limits)

This keeps the subscriber list close to its actual state, without wastefully retrying delivery to expired subscriptions.

Push delivery to browsers is based on the Web Push protocol (RFC 8291 encryption, RFC 8188 message encoding, RFC 8292 VAPID). todoke implements these itself and does not depend on the common Node.js web-push library (in order to remain compatible with the Web Crypto API of the Cloudflare Workers runtime).

As a user, there are mainly two concepts you need to be aware of.

Subscription information obtained from the browser’s Push API, consisting of the following three values.

FieldDescription
endpointThe delivery-target Push service URL (differs by browser and vendor)
p256dhThe public key used for payload encryption
authThe authentication secret used for payload encryption

By registering these three together with the API, todoke becomes able to send encrypted notifications to that browser.

VAPID (Voluntary Application Server Identification) is a mechanism for proving to the Push service which application a send originates from. When an app is created, todoke issues a per-app VAPID key pair (public and private keys) and stores the private key encrypted. At send time, it attaches a JWT signed with this key pair to each request to prove the sender to the Push service.

The public key is needed when creating a subscription on the browser side, so it can be obtained from GET /api/v1/vapid-public-key.

The Free plan has limits on the subscriber count and the monthly send count. These are checked at different times.

LimitWhen it is checkedLimit (Free)
Monthly send countWhen sending a notification (before enqueuing to the Queue)30,000 sends
Subscriber countWhen registering a subscription1,000 subscribers

In other words, the monthly send limit is checked independently on each POST /api/v1/notify request, and the subscriber limit is checked on each POST /api/v1/apps/:appId/subscriptions request that registers a new subscription. If either is exceeded, the request is rejected and the response includes an upgrade link (upgrade_url). See Error codes and limits for details.