KasyKasy

Push Notifications

FCM across all three backends. Automatic setup, APNs on iOS, deep links, and images in the notification.

Push uses Firebase Cloud Messaging (FCM) across all three backends. FCM delivers to the device; your backend decides when and to whom to send.

BackendHow it fires
FirebaseCloud Function on insert into users/{uid}/notifications/{id}
SupabaseEdge Function via a webhook on the notifications table
REST APIYour server calls FCM over HTTP

Implementation

Interactive kasy new configures what it can up front. In Quick mode, credentials are deferred: Android works with no extra step on the emulator. iOS needs an APNs Key (all backends). Supabase + REST API need the Firebase service account.

Via MCP: after setup, ask "check my project's push configuration" (check_project) or "check and fix push" (check_project with fix). See Kasy MCP.

iOS: APNs Key (all backends)

The APNs Key only exists in the Apple Developer Portal and needs to be uploaded to the Firebase Console. One key serves every app under the same Team ID.

1. Create the key in the Apple Developer Portal

  1. Go to developer.apple.com → Keys → "+"
  2. Give it a name (e.g., Firebase APNs Key)
  3. Enable Apple Push Notifications service (APNs)
  4. Click Continue → Register
  5. Download the .p8: you can only download it once, keep it somewhere safe
  6. Note the Key ID and the Team ID (top-right corner of the portal)

Tip: also enable Sign In with Apple on the same key. That way you have a single .p8 for both push and Apple login.

2. Upload it to Firebase Console

  1. Firebase Console → Cloud MessagingApple app configuration section
  2. Select the iOS app → click Upload
  3. Select the .p8, fill in Key ID and Team ID
  4. Repeat for Development and Production (same file)
APNs Authentication Key configured in Firebase Cloud Messaging
Firebase Console: Cloud Messaging → Apple app configuration with the Development and Production keys

3. Verify

Admin Console → Tools → Send test notification. On iOS, the app needs push permission granted.

kasy check          # diagnostics: secrets, edge functions, IAM
kasy check --fix    # fixes automatically
kasy deploy         # redeploy the functions

Via MCP: "check the project" (check_project, with optional fix), "deploy the backend" (deploy_backend).

Supabase and REST API: Firebase Service Account key

On the Firebase backend, push works "keyless" because the server uses the Google Cloud project's default credentials (ADC). On Supabase and REST API, the server is external to Firebase: you need to generate and register a JSON key.

1. Generate the key in Firebase Console

  1. Open the Firebase Console → your project
  2. Project settings → Service accounts
  3. Generate new private keyJSONCreate
  4. Save the file (a ~1 KB JSON with type, project_id, private_key, etc.)
Generate new private key dialog in the Firebase Admin SDK
Firebase Console: Project settings → Service accounts → Generate new private key

2. Register it in the backend

BackendHow to register it
Supabasekasy deploy creates the service account and stores FIREBASE_SERVICE_ACCOUNT_JSON and FIREBASE_PROJECT_ID. Manual later: supabase secrets set FIREBASE_SERVICE_ACCOUNT_JSON='<JSON content>' and supabase secrets set FIREBASE_PROJECT_ID=your-id
REST APILoad the JSON on your server and use the Firebase Admin SDK (Node, Python, Go, etc.) to call firebase-admin.messaging().send()

Never put the JSON key in the Flutter app or in the client's .env. It goes ONLY on the server.

Information

Sending notifications

To one user: Firebase (Firestore)

// users/{userId}/notifications/{id}
{
  title: "Title",
  body: "Message body",
  type: "OTHER",
  notify_user: true,
}

To one user: Supabase (SQL)

INSERT INTO public.notifications (user_id, title, body, type)
VALUES ('user-uuid', 'Title', 'Body', 'OTHER');

To all users: Supabase

INSERT INTO public.notifications (user_id, title, body, type)
SELECT DISTINCT ON (d.user_id) d.user_id,
  'Title', 'Body', 'OTHER'
FROM public.devices d
ORDER BY d.user_id, d.creation_date DESC;

From the app (Admin Console)

Settings → Admin → Tools → Send notification. Lets you send to a specific email or to everyone, with an image and a target screen.

The data field controls where the app navigates:

dataBehavior on tap
emptyOpens the Notifications list
{"route": "/premium"}Navigates to the internal screen
{"url": "https://..."} + type: LINKOpens in the external browser

The route field accepts any GoRouter path. It works in any app state (foreground, background, closed).

Examples:

-- Takes the user to the subscription screen
INSERT INTO public.notifications (user_id, title, body, type, data)
VALUES ('uuid', 'Offer!', 'Tap to see.', 'OTHER', '{"route": "/premium"}');

-- Opens an external link
INSERT INTO public.notifications (user_id, title, body, type, data)
VALUES ('uuid', 'New!', 'Check it out on the site.', 'LINK', '{"url": "https://yoursite.com/whats-new"}');

Dynamic deep link (e.g., opening a specific post)

Same idea, but with a route parameter:

  1. Create the route in router.dart: /post/:id
  2. Send {"route": "/post/123"} in the data field

Each notification points to different content, same behavior as Twitter and Instagram. The plumbing (navigation, back stack, every app state) already comes ready.

Image in the notification

Include image_url with a public JPEG/PNG URL:

// Firebase
{
  title: "Offer!",
  body: "Tap to see.",
  image_url: "https://example.com/image.jpg",
  type: "OTHER",
  notify_user: true,
}
-- Supabase
INSERT INTO public.notifications (user_id, title, body, type, image_url)
VALUES ('uuid', 'Offer!', 'Tap to see.', 'OTHER', 'https://example.com/image.jpg');

Shows up as a thumbnail in the system banner (iOS needs UNNotificationServiceExtension, which already ships in the project under ios/NotificationService/).

Behavior by app state

StateWhat happens
Foreground (app open)Local banner + notification saved to the in-app list
Background (minimized)OS banner
ClosedOS banner; tapping opens the app to the right screen

Notes

  • The Android emulator needs a Google account configured on it to generate a valid FCM token.
  • iOS with a debugger attached can block background push. Press d in the terminal to detach the debugger while keeping the app running.
  • The .p8 key can only be downloaded once: keep it safe.

Last updated on 08/02/2026