Channels
Email, in-app and push, web, iOS, macOS, Android and Windows. Each channel is set up from the dashboard, and is on once it has settings.
Every channel starts off. It turns on when you save its settings on the Channels page of the
dashboard, and each type’s template turns it on for that type. Removing a channel’s settings turns
it off again: its sends skip it as channel_not_configured, and the deliveries it still had queued
fail with that reason. A pause is something else: it holds a channel’s deliveries until you resume
it; see Pause a channel.
Settings are saved only through the dashboard, so a new environment sends nothing, not even in-app,
until Cloudflare Access covers its URL and someone saves its channels; under wrangler dev, the
identity under access.dev signs you in. Each environment’s Chaski keeps settings of its own, and
the push credentials among them are encrypted with CHASKI_SECRET. A setting that stops working, such as credentials saved under
another CHASKI_SECRET, shows its channel as Failing until you save it again.
Configuration lists every field.
Email goes out through Cloudflare Email Service, from a domain you onboarded to it, through the
CHASKI_EMAIL binding. Set its sender address, and optionally a sender name and a reply-to
address. The address must be one the binding allows. The page says when the binding is
missing.
A send can override the sender name and the reply-to address, and attach up to 10 files when
CHASKI_ATTACHMENTS holds an R2 bucket. The HTML of every email carries a pixel, served through
your app’s userRoutes(), which marks the delivery opened when the message is read; set
CHASKI_PUBLIC_URL to that mount, and without it emails carry no pixel.
A contact without an email address skips the channel with missing_email.
In-app
The in-app channel keeps a feed per contact in the ChaskiFeed Durable Object, and pushes each new
item to their open pages over a WebSocket. It has no settings: turn it on, with CHASKI_FEED
bound. Your frontend reads the feed through your app’s userRoutes(), as
Browser clients shows; on the server, env.CHASKI.feed() lists it, counts
the unread items and marks them read.
Push
Push reaches browsers through web push, Apple devices through APNs, Android devices through FCM and Windows devices through WNS. Set up any of them; push is on once one is.
- Web push takes a contact, a
mailto:orhttps:address the push services can reach you at, and a key pair: Generate keys makes one, and Use existing keys takes the pair you already send with. - APNs takes your team id, the key’s id and the
.p8file, which send to every app of your team. The iOS app and the macOS app each take their bundle ID and the APNs environment of their tokens: Sandbox for development builds, Production for TestFlight and App Store builds. - FCM takes a service account’s JSON key file, from Project settings → Service accounts in the Firebase console. The project it names must be the one your Android app registers its tokens with.
- WNS takes the Microsoft Entra app registration your Windows app creates its push channel
with, through the Windows App SDK’s
PushNotificationManager: its tenant id, its client id and one of its client secrets.
A contact receives push on the devices registered to them. Browsers register with
client.push.enable() from chaskijs/client; your app can register an iOS or macOS device token,
an Android FCM token or a Windows channel URI from the server:
await env.CHASKI.push().register({
recipient: { id: user.id },
platform: 'android', // or 'ios', 'macos', 'windows'
deviceToken,
});A mobile app can also register its own token through your app’s userRoutes(), with
client.push.registerDevice(), as the signed-in user.
An Apple token belongs to the environment of the build that made it: a development build’s token
only works in sandbox. Pass environment: 'sandbox' when that build registers, and the app’s
environment in the settings applies to every other device. When APNs refuses a token as
BadDeviceToken or DeviceTokenNotForTopic, Chaski forgets the device and logs the reason to
Workers Logs: the token may be fine and the environment or the bundle id wrong.
An Electron app on macOS gets its token from pushNotifications.registerForAPNSNotifications()
in the main process, and registers it with client.push.registerDevice({ platform: 'macos', deviceToken }), as Browser clients shows.
On Windows, the device token is the channel URI WNS hands the app. Chaski only accepts one on
notify.windows.com, since it sends its access token there. The push arrives as a toast whose
launch arguments carry the href and tag, which the Windows App SDK hands the app as
AppNotificationActivatedEventArgs.Arguments when the user clicks it. WNS channels expire after
30 days, so the app registers its channel again on every launch; Chaski forgets one WNS answers
404 or 410 for.
A send with push: { silent: true } shows its alert without a sound, on Apple devices, on Windows
and in browsers. On Android, the notification channel your app creates decides the sound.
Every push carries the title, the body, the link (href) and a tag: the send’s
push.collapseId, or the notification id, which the feed item also
carries as notificationId. A push with the tag of one already shown replaces it, on Apple
devices, Android and in browsers. The send’s data comes along as is:
| Platform | Where href, tag and data arrive |
|---|---|
| Web push | The push’s JSON: { title, body, href, tag, silent, data }. |
| iOS and macOS | Custom keys next to aps, one per data key. |
| Android | The message’s data, one per data key. |
| Windows | The toast’s launch arguments, one per data key. |
Give your app’s own notification of the same event the same id, collapseId or the notification
id, and the system shows one of the two. An app that shows the feed’s items itself while it runs
can also take its device out of push while its feed is open; see
Browser clients.
On Android the notification shows on its own when the app is in the background; the href and
tag of the push arrive in the intent’s extras when the user taps it.
A contact without a registered device skips the channel with no_device_tokens.
Updated Oct 30, 2026
MIT License · chaski.sh