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.
Overall Structure
Section titled “Overall Structure”todoke is composed of the following Cloudflare components.
| Component | Role |
|---|---|
| Cloudflare Workers | Runtime for the REST API and Queue Consumer |
| Cloudflare D1 | Persistence for apps, subscribers, API keys, delivery logs, and more (SQLite) |
| Cloudflare KV | Temporary storage for sessions and OAuth state |
| Cloudflare Queues | Buffering and asynchronous delivery of notifications |
| Cloudflare Pages | Hosting 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.
How a Notification Is Delivered
Section titled “How a Notification Is Delivered”Notification delivery follows this path.
REST API → Cloudflare Queues → Queue Consumer Worker → Push service (FCM, etc.) → browser- A client (SDK, CLI, curl, etc.) calls
POST /api/v1/notify - The API validates the payload size, plan limits, and so on, then enqueues the notification into Cloudflare Queues
- The API returns
202 Acceptedas soon as the enqueue completes - 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.
Retries and the DLQ (Dead Letter Queue)
Section titled “Retries and the DLQ (Dead Letter Queue)”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
Automatic Subscription Deactivation
Section titled “Automatic Subscription Deactivation”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.
Basics of Web Push / VAPID
Section titled “Basics of Web Push / VAPID”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 (Push Subscription)
Section titled “Subscription (Push Subscription)”Subscription information obtained from the browser’s Push API, consisting of the following three values.
| Field | Description |
|---|---|
endpoint | The delivery-target Push service URL (differs by browser and vendor) |
p256dh | The public key used for payload encryption |
auth | The 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 Key Pair
Section titled “VAPID Key Pair”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.
Where Plan Limits Are Applied
Section titled “Where Plan Limits Are Applied”The Free plan has limits on the subscriber count and the monthly send count. These are checked at different times.
| Limit | When it is checked | Limit (Free) |
|---|---|---|
| Monthly send count | When sending a notification (before enqueuing to the Queue) | 30,000 sends |
| Subscriber count | When registering a subscription | 1,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.