AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Push Notifications

skill-ampli-group-agentic-mobile-blueprint-push-notifications · by Ampli-Group

Set up push notifications for iOS and Android using Expo Notifications and Supabase Edge Functions. Use when adding push notifications, configuring APNs/FCM credentials, sending notifications from the backend, or troubleshooting notification delivery.

No reviews yet
0 installs
34 views
0.0% view→install

Install

$ agentstack add skill-ampli-group-agentic-mobile-blueprint-push-notifications

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-ampli-group-agentic-mobile-blueprint-push-notifications)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Push Notifications? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Push Notifications

Stack

  • expo-notifications — handles token registration, foreground/background receipt on device
  • Expo Push Notification Service (EPN) — proxies to APNs (Apple) and FCM (Google); free, no quota
  • Supabase Edge Function — sends notifications server-side via Expo Push API
  • APNs key — required for iOS production builds (configured in Expo dashboard)
  • FCM key — required for Android (configured in Expo dashboard)

You do NOT call APNs or FCM directly. Expo's push service handles the routing.


Step 1: Install

cd mobile
npx expo install expo-notifications expo-device expo-constants

Add to app.json:

{
  "expo": {
    "plugins": [
      [
        "expo-notifications",
        {
          "icon": "./assets/notification-icon.png",
          "color": "#ffffff",
          "sounds": ["./assets/notification-sound.wav"]
        }
      ]
    ]
  }
}

Step 2: Register for Push Token

Add to your app initialization (e.g. app/_layout.tsx):

import * as Notifications from "expo-notifications";
import * as Device from "expo-device";
import Constants from "expo-constants";

// Handle notifications when app is in foreground
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: false,
  }),
});

export async function registerForPushNotifications(): Promise {
  if (!Device.isDevice) {
    console.warn("Push notifications require a physical device");
    return null;
  }

  const { status: existingStatus } = await Notifications.getPermissionsAsync();
  let finalStatus = existingStatus;

  if (existingStatus !== "granted") {
    const { status } = await Notifications.requestPermissionsAsync();
    finalStatus = status;
  }

  if (finalStatus !== "granted") {
    return null;
  }

  const projectId = Constants.expoConfig?.extra?.eas?.projectId;
  const token = (await Notifications.getExpoPushTokenAsync({ projectId })).data;

  return token;   // looks like: ExponentPushToken[xxxxx]
}

projectId comes from eas.json or app.jsonexpo.extra.eas.projectId. Find it in expo.dev under your project settings.


Step 3: Store Token in Supabase

After registration, save the token to your database:

// lib/notifications.ts
import { supabase } from "./supabase";
import { registerForPushNotifications } from "./pushNotifications";

export async function syncPushToken(userId: string) {
  const token = await registerForPushNotifications();
  if (!token) return;

  await supabase.from("push_tokens").upsert(
    { user_id: userId, token, platform: Platform.OS },
    { onConflict: "token" }
  );
}

Database schema:

create table push_tokens (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users not null,
  token text unique not null,
  platform text not null,  -- ios | android
  created_at timestamptz default now()
);

alter table push_tokens enable row level security;
create policy "users manage own tokens" on push_tokens
  using (auth.uid() = user_id)
  with check (auth.uid() = user_id);

Call syncPushToken after login:

supabase.auth.onAuthStateChange((event, session) => {
  if (event === "SIGNED_IN" && session?.user) {
    syncPushToken(session.user.id);
  }
});

Step 4: Send Notifications from Edge Function

// supabase/functions/send-notification/index.ts
import { createClient } from "npm:@supabase/supabase-js@2";

const EXPO_PUSH_URL = "https://exp.host/--/api/v2/push/send";

interface PushMessage {
  to: string;
  title: string;
  body: string;
  data?: Record;
  sound?: "default" | null;
  badge?: number;
}

export async function sendPushNotification(
  userIds: string[],
  notification: Omit
) {
  const supabase = createClient(
    Deno.env.get("PUBLIC_SUPABASE_URL")!,
    Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
  );

  const { data: tokens } = await supabase
    .from("push_tokens")
    .select("token")
    .in("user_id", userIds);

  if (!tokens?.length) return;

  const messages: PushMessage[] = tokens.map(({ token }) => ({
    to: token,
    sound: "default",
    ...notification,
  }));

  // Expo accepts up to 100 messages per request
  const chunks = chunk(messages, 100);
  for (const batch of chunks) {
    await fetch(EXPO_PUSH_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(batch),
    });
  }
}

function chunk(arr: T[], size: number): T[][] {
  return Array.from({ length: Math.ceil(arr.length / size) }, (_, i) =>
    arr.slice(i * size, i * size + size)
  );
}

Step 5: Configure APNs Key (iOS Production)

Required for iOS push notifications in production builds.

  1. developer.apple.com/account/resources/authkeys+
  2. Name: Expo Push, enable Apple Push Notifications service (APNs)
  3. Download the .p8 file — download once only
  4. Note the Key ID and your Team ID (top right of developer.apple.com)

Upload to Expo:

  1. expo.dev → Your project → CredentialsiOS
  2. Push Notifications → Upload the .p8 key, enter Key ID and Team ID

Or via CLI:

eas credentials --platform ios
# Select: Push Notifications → Upload APNs key

Step 6: Configure FCM (Android Production)

  1. Go to console.firebase.google.com
  2. Create project (or use existing) → Add app → Android
  3. Enter your package name (com.yourorg.appname)
  4. Download google-services.json → place in mobile/google-services.json
  5. Project settingsCloud Messaging → copy Server key

Add to app.json:

{
  "expo": {
    "android": {
      "googleServicesFile": "./google-services.json"
    }
  }
}

Upload FCM key to Expo:

eas credentials --platform android
# Select: Push Notifications → Upload FCM key

Handle Notification Taps

import { useEffect, useRef } from "react";
import * as Notifications from "expo-notifications";
import { router } from "expo-router";

export function useNotificationNavigation() {
  const responseListener = useRef();

  useEffect(() => {
    // App opened from notification
    responseListener.current = Notifications.addNotificationResponseReceivedListener(
      (response) => {
        const data = response.notification.request.content.data;
        if (data?.screen) {
          router.push(data.screen as string);
        }
      }
    );

    return () => {
      responseListener.current?.remove();
    };
  }, []);
}

Pass data: { screen: "/orders/123" } when sending to deep-link on tap.


Testing

# Test with Expo CLI (dev build or Expo Go)
npx expo push:send --to "ExponentPushToken[xxx]" --title "Test" --body "Hello"

# Or use the Expo push tool
open https://expo.dev/notifications

Physical device required for testing — simulator/emulator does not receive push notifications.


Gotchas

Notifications work in Expo Go but not in production build — APNs key not uploaded to Expo. Run eas credentials --platform ios and verify Push Notifications key is configured.

Android notifications not deliveredgoogle-services.json missing or wrong package name. Verify package in app.json matches Firebase project.

Token is null on simulator — Expected. Push tokens only work on physical devices. Check with Device.isDevice.

"DeviceNotRegistered" error — Token is stale (user uninstalled/reinstalled app). Delete the token from push_tokens table when this error is returned by Expo Push API.

Notification not shown when app is in foreground — Set shouldShowAlert: true in setNotificationHandler. By default foreground notifications are silent.

iOS notification permission denied — You can only ask for permission once. If denied, user must go to Settings manually. Always explain why before requesting.

Badge count not clearing — Call Notifications.setBadgeCountAsync(0) on app foreground:

AppState.addEventListener("change", (state) => {
  if (state === "active") Notifications.setBadgeCountAsync(0);
});

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.