Two channels, one skill (`notify-user`). Email path uses the same `send_email` binding as the email plugin; Web Push path is a from-scratch implementation in `src/webpush/` covering ECDSA P-256 VAPID JWT signing, ECDH-ES + HKDF + AES-128-GCM payload encryption, and 410/404-driven auto-pruning of dead subscriptions. Browsers fetch the VAPID public key unauthenticated from `/webpush/public-key`; subscriptions land in D1 `push_subscriptions` after Access-gated POST to `/webpush/subscribe`. Every notification is audit-logged in D1 `notifications`.
Install
1 · Config
Append to ENABLED_PLUGINS in wrangler.toml:
notifier
2 · Secrets
Run each from your Worker project:
-
wrangler secret put VAPID_PUBLIC_KEYoptional — Web Push public key (uncompressed P-256, base64url). Generate with `npm run vapid:generate`. -
wrangler secret put VAPID_PRIVATE_KEYoptional — Web Push private scalar (32 bytes, base64url). Keep as a Worker secret. -
wrangler secret put VAPID_SUBJECToptional — VAPID `sub` claim — `mailto:you@example.com` or an https URL.
Copy-paste .dev.vars template
VAPID_PUBLIC_KEY=# optional — Web Push public key (uncompressed P-256, base64url). Generate with `npm run vapid:generate`. VAPID_PRIVATE_KEY=# optional — Web Push private scalar (32 bytes, base64url). Keep as a Worker secret. VAPID_SUBJECT=# optional — VAPID `sub` claim — `mailto:you@example.com` or an https URL.
3 · Steps
- Decide your channels: email-only is fine without VAPID. For Web Push you need all three VAPID secrets.
- npm run vapid:generate — copy the two values, then `wrangler secret put VAPID_PUBLIC_KEY` and VAPID_PRIVATE_KEY
- wrangler secret put VAPID_SUBJECT ← e.g. mailto:you@example.com
- Browser side: GET /webpush/public-key, then PushManager.subscribe(...), then POST /webpush/subscribe
- Test: curl -X POST https://<worker>/webpush/test -H 'cf-access-jwt-assertion: <token>'