# Push notifications and the relay

> How Frame delivers iPhone and Android notifications for self-hosted servers through Crow Tech's content-blind push relay, what the relay can and can't see, and how to turn push off.

Source: https://crow.sg/apps/frame/docs/push/

Apple and Google only deliver notifications that are signed with the **app publisher's** keys, and self-hosted servers can't have those. So your server hands notifications to a small relay run by Crow Tech, the Frame publisher, at `https://frame.crow.sg`, which passes them on to Apple (APNs) for iPhones and to Google (FCM) for Android phones. Nothing to set up: it's on by default.

## What the relay can see

The relay is built to learn as little as possible:

- **The content is end-to-end encrypted.** Each phone makes its own key and shares it only with your server. The relay, Apple and Google only see ciphertext plus a generic "Frame: New activity". The phone decrypts the notification and shows the real text and thumbnail.
- **The relay stores nothing.** It has no database and doesn't log device tokens, payloads or IP addresses.
- **Your server never sees the phones' real push tokens**, only sealed relay tokens that only the relay can open. Your server can't use them to push to anyone else's device.

What the relay *can* know is that some server, identified by its IP address, asked it to deliver a notification of a certain size at a certain time. It could also drop or delay notifications. It can't read content or learn who your users are.

## Push permission expires

Each phone's relay token stops working after **60 days**. The apps fetch a fresh one every time they open and send it to your server, so phones that are in use never notice. If someone uninstalls the app or stops using a server, that server loses the ability to notify them after at most 60 days. Signing out removes it immediately.

## Widgets

New photos also refresh your friends' home-screen widgets. On iPhone (iOS 26 and later) the server sends a separate widget push for every new moment, so widgets update even when notifications are muted. Without push, widgets refresh about every 15 minutes.

## Turn push off

Set an empty value and restart:

```ini title="frame.conf"
FRAME_PUSH_RELAY=
```

The apps still work. Widgets refresh about every 15 minutes, and the app updates live while it's open. Notifications for new messages only arrive when the app is opened. Running your own relay only makes sense if you also build and sign the apps yourself; the protocol is in [PUSH.md](https://github.com/Crow-Tech-Pte-Ltd/frame/blob/main/docs/PUSH.md).

## When notifications don't arrive

- Check that `FRAME_PUSH_RELAY` isn't empty. The admin panel's **Overview** shows whether the relay is reachable.
- Check that the server can reach the internet:

  ```sh
  curl -sI https://frame.crow.sg/healthz      # can the server reach the relay?
  journalctl -u frame | grep "push failed"    # or: docker compose logs frame | grep "push failed"
  ```

- On the phone, allow notifications for Frame. On Android, also turn off battery optimisation for Frame. On iOS, check Focus modes.
