edgepush.dev

One API for
iOS and Android
push.

edgepush is an open-source push notification API for iOS and Android, running on Cloudflare Workers. Bring your own APNs and FCM credentials, send from one endpoint, own your delivery data. Free hosted tier or self-host on your account.

· no credit card · agpl-3.0 + mit

├  server.ts
$ example
// rich notification, collapse-id, expiration.
// the full APNs surface, on a single line per field.

import { Edgepush } from "@edgepush/sdk";

const edge = new Edgepush({
  apiKey: process.env.EDGEPUSH_API_KEY,
});

await edge.send({
  to:              "a1b2c3d4…",
  title:           "New order #4271",
  body:            "2x flat white, table 3",
  image:           "https://cdn.acme.app/o/4271.jpg",
  mutableContent:  true,  // iOS NSE
  collapseId:      "order-4271",  // replace prior
  expirationAt:    Date.now() / 1000 + 600,
});

// → ticket.id: tk_01HX2A9P4M   queued

Every send tracked in real time. Delivered, retried, failed, queued.

tail -f push-events.log $ example
2026-04-11T04:25:12.847Z io.acme.pos ● delivered
2026-04-11T04:25:11.402Z app.orbit.ios ● delivered
2026-04-11T04:25:10.219Z com.ridehaus.android ○ queued
2026-04-11T04:25:09.113Z io.acme.pos ● retry 1/3
2026-04-11T04:25:07.005Z com.pixeldiner ● failed
2026-04-11T04:25:04.882Z app.orbit.ios ● delivered
─  quickstart
send from any runtime
├  node.ts @edgepush/sdk
// npm install @edgepush/sdk
import { Edgepush } from "@edgepush/sdk";

const client = new Edgepush({ apiKey: "com.acme.app|sk_…" });

await client.send({
  to:    "a1b2c3d4…",
  title: "Hello",
  body:  "From Node",
});
├  curl POST /v1/send
# any HTTP client works
curl https://api.edgepush.dev/v1/send \
  -H "authorization: Bearer com.acme.app|sk_…" \
  -H "content-type: application/json" \
  -d '{
    "messages": [{
      "to": "a1b2c3d4…",
      "title": "Hello",
      "body": "From curl"
    }]
  }'
react native? bun? deno? same SDK. needs only fetch.
─  how it works
├  01

bring your credentials

Upload your APNs .p8 key and Firebase service account JSON in the dashboard. Both are encrypted with AES-GCM before being written to D1.

├  02

generate an api key

Keys are scoped to a single app and prefixed with the package name so they self-identify in logs: com.acme.myapp|sk_...

├  03

send from anywhere

Use the SDK or POST directly to /v1/send. Dispatched through Cloudflare Queues with automatic retries and a dead letter queue. iOS, Android, topics, rich images, collapse, voip. One call.

─  webhook events
HMAC-signed POST

Push your delivery state.

Configure a webhook URL in the dashboard and edgepush will POST every state change to your endpoint with an HMAC-SHA256 signature over the raw body. No polling required.

  • message.delivered
  • message.retry
  • message.failed
  • message.invalid_token
├  POST /your/webhook 200 OK
// headers
x-edgepush-event:     message.delivered
x-edgepush-signature: sha256=9f86d081…
x-edgepush-id:        evt_01HX2A9P4M

// body
{
  "event": "message.delivered",
  "id":    "tk_01HX2A9P4M",
  "app":   "com.acme.app",
  "to":    "a1b2c3d4…",
  "platform": "ios",
  "latency_ms": 192
}
─  built for production

fully open source

AGPL-3.0 server, MIT SDK and CLI. Self-host on your own Cloudflare account, two wrangler deploys, no telemetry. Fork the repo and own it forever.

native tokens

No proprietary token format. You use real APNs device tokens and FCM registration tokens. Migrate in and out at will.

delivery receipts

Every send returns a ticket id. Poll the receipt or subscribe to webhooks for delivered and failed events.

rate limiting built-in

Per-app token bucket via Durable Objects. Survives Worker restarts, scoped to each app, retries with backoff on overflow.

webhook events

HMAC-signed POSTs when a message is delivered, retried, or failed. Invalid tokens are flagged on the receipt so your code can prune them.

encrypted credentials

Your APNs .p8 and FCM service account JSON are encrypted at rest. The raw key is never exposed via the API.

─  how it compares
edgepush vs hosted push providers
edgepush expo push onesignal knock
open source yes no no no
self-hostable yes no no no
byo apns + fcm yes no yes yes
native token format yes no (proprietary) yes yes
rich notifications (images) yes no yes yes
apns collapse-id yes no yes yes
fine-grained apns push types (voip, location, ...) yes no partial partial
reliable silent / background push yes best effort yes yes
absolute notification expiration yes no yes yes
delivery receipts + retries + dlq yes best effort yes yes
fcm topic / condition targeting yes no yes yes
webhook retry with backoff yes (3x) no yes yes
hard rate ceiling per-app, configurable 600/sec global n/a n/a
runs on cloudflare workers yes no no no
per-message pricing no no (rate-limited) yes yes
vendor lock-in none high medium medium
cost (10k pushes/mo) $0 $0 ≈ $0 ≈ $25

Comparison reflects publicly documented pricing and features as of April 2026. Other providers may have changed; check their docs.

─  pricing
full pricing →
├  free
$0 / forever
edgepush.dev hosted

No credit card. The fastest way to try edgepush against a real device. Sized for side projects and indie apps that haven't shipped yet.

  • 1 app
  • 10K events / month
  • 7-day delivery log
  • BYO apns + fcm credentials
  • delivery receipts + webhooks
  • support via github issues
$ sign_in_with_github
├  pro
$29 / month
edgepush.dev hosted

For indie shippers running a few apps in production. Same infrastructure as free, just with the room to actually use it. Priority email support direct from the operator.

  • 3 apps
  • 50K events / month
  • 14-day delivery log
  • everything in free, plus:
  • priority email support
  • credential health alerts
$ upgrade_to_pro
├  self-host
$0 / forever
your cloudflare account

Same code, your infra, your data. Cloudflare's free tier covers most apps. You own the credentials, the rate limits, the deploy cadence. No edgepush.dev in the middle.

  • unlimited apps + events
  • 2 wrangler deploys to go live
  • agpl-3.0 server source
  • your D1, KV, queue, DO
  • your apns + fcm credentials
  • no telemetry, no operator
$ self_host_guide
─  faq

I already have APNs and FCM credentials. Does edgepush use them?

Yes, that's the whole point. Upload your existing .p8 key and Firebase service account JSON. edgepush encrypts and stores them. You keep full ownership.

How are my credentials protected?

Encrypted with AES-GCM in a Cloudflare D1 row scoped to your app. The encryption key is a Worker secret, never in the database. The raw credential is never returned by the API after upload.

How do I migrate from Expo Push?

Switch your app from getExpoPushTokenAsync to getDevicePushTokenAsync (one function name change), point your server at /v1/send. That's the entire migration. edgepush uses native APNs and FCM tokens, not Expo's proprietary wrapper.

What are the rate limits?

1000 pushes per minute per app (configurable). Per-app token bucket via a Durable Object. Self-hosters can tune the limit to anything they want.

Do I need to store device tokens with edgepush?

No. edgepush is a dispatch layer. You manage tokens in your own database. Each receipt tells you when a token is invalid so you can prune it.

Is this stable enough for production?

v1.0 ships the full pipeline: send, dispatch, receipts, webhooks, credential health probes, nightly backups, Stripe billing, per-app rate limits, webhook retry queue, FCM topics. 70 unit tests. Semver from here.

Why does this run on Cloudflare?

Workers + D1 + Queues + Durable Objects is the only stack where the entire push pipeline runs on free-tier edge primitives. No cold starts. No per-region routing. One runtime, globally.

What about web push or email?

edgepush does native iOS and Android push. That's the scope. For multi-channel orchestration, use a dedicated tool alongside edgepush.

ready_to_ship

Ship pushes by end of day.

Free hosted tier. Pro is $29/mo when you outgrow it. Or run the whole stack on your own Cloudflare account.