Mar 13, 2025

Articles

Building a Serverless Push Notification System with Supabase Edge Functions

Paul Waweru

Introduction

Push notifications are the lifeblood of modern apps – they bring users back, deliver timely updates and increase engagement. However, here's the challenge - most developers default to third-party services like OneSignal or CleverTap which adds extra costs and dependencies to your project.

What if you could build your own serverless push notification system that you fully control?

This guide walks you through creating a complete push notification system using Supabase Edge Functions and Apple Push Notification Service (APNs) for iOS apps built with Expo. You'll gain full control over your notification infrastructure while keeping costs down and ensuring speedy delivery.

Note: This guide focuses on iOS integration. We'll cover Android/FCM implementation in a future article.

You can access the codebase used in this project here.

Prerequisites

Before diving in, make sure you have:

  • Expo experience: Familiarity with expo-notifications and handling permissions

  • Supabase knowledge: Understanding of Edge Functions creation, deployment, and execution using the Supabase CLI

  • Physical iOS device: The iOS simulator doesn't support push notifications

  • Active iOS Developer Account: Required for generating APNs authentication keys

Why Supabase for Push Notifications?

You might wonder why build on Supabase instead of using existing notification services. Here's why:

  • Complete control: Using Supabase allows full ownership of the notification infrastructure without depending on external services.

  • Automatic scaling: Edge Functions scale automatically with demand, eliminating the need to manage infrastructure scaling manually.

  • Performance optimisation: Edge Functions typically run in data centers closer to users' geographical locations, which can reduce latency for notification delivery.

  • Cost efficiency: The pay-per-execution model can be more cost-effective than subscription-based notification services, especially for applications with variable notification volumes.

System Overview

Our notification system consists of five key components:

  1. APNs Authentication: Secure connection to Apple's notification service

  2. Supabase Database: Storage for device tokens and user notification preferences

  3. Supabase Edge Function: Handler for push notification delivery

  4. Client Integration: Permission management and device token registration

  5. Trigger Mechanism: Event-based notification dispatching

Implementation Steps

1. Configuring APNs Authentication

To send iOS notifications, you first need to generate an APNs key:

  1. Go to App Store Connect > Certificates, Identifiers & Profiles

  2. Navigate to the Keys section and create a new key

  3. Enable Apple Push Notifications service

  4. Give your key a descriptive name and choose the appropriate environment:

    1. Sandbox for development

    2. Production for live apps

  5. Download the .p8 authentication key file (important: you can only download this once)

  6. Store these key details securely – you'll need them for your Edge Function

You can check out the following video for a step-by-step guide on creating your APNs key.

2. Supabase Database Schema Design

Next, let's set up a table in Supabase to store device tokens and notification preferences:

CREATE TABLE public.registered_devices (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    device_token VARCHAR NOT NULL,
    device_type VARCHAR NOT NULL,
    enabled_notifications BOOLEAN NOT NULL DEFAULT TRUE,
    UNIQUE(device_token)
)

This schema includes:

  • User reference with appropriate foreign key constraints

  • Device token for push notification addressing

  • Platform identification for multi-platform support

  • Toggle mechanisms for notification preferences

  • Timestamp fields for auditing and analytics

For proper security, implement row-level security (RLS) policies:

-- Enable RLS
ALTER TABLE public.registered_devices_example ENABLE ROW LEVEL

3. Supabase Edge Function Implementation

The heart of our system is the Edge Function that will:

  • Generate authentication tokens for APNs

  • Retrieve registered device tokens from the database

  • Format and send notifications to the appropriate endpoints

Environment Configuration

First, set up the necessary environment variables. Here's what you'll need (you can see the example env file here):

  • FUNCTIONS_SUPABASE_URL & FUNCTIONS_SUPABASE_SERVICE_ROLE_KEY: Found in your Supabase project under API settings.

  • APNS_AUTH_KEY_BASE64: Convert your .p8 authentication key downloaded earlier from Apple Developer to a Base64 string using the following command:

cat AuthKey_XXXXXXXXXX.p8 | base64
  • APNS_KEY_ID: The identifier shown when you created your APNs key



  • APNS_TEAM_ID: Found in your Apple Developer account homepage (a 10-character alphanumeric string):



  • APNS_BUNDLE_ID: Your app's bundle ID, which must match the registered app in your Apple Developer account:


Edge Function Entry Point

Next, define the main entry point of our Supabase Edge Function. This function will handle incoming requests, send a test notification, and return the result. You can find the example of the entry point here.

Sending Push Notifications to iOS devices

The core logic for issuing push notifications can be found here and includes the following:

  • Fetches registered devices in the registered_devices table we created earlier using the supabase syntax

  • Retrieves the Base64-encoded .p8 key from environment variables

  • Converts it into a cryptographic key using crypto.subtle.importKey

  • Generates a JSON Web Token (JWT) to authenticate with Apple Push Notification Service (APNs) - all of which can be found here

  • Sends the notification payload to all devices in parallel

  • Returns the response from APNs for each device - all of which can be found here

4. Client Integration

The following steps focus on the iOS Expo app which will receive the push notifications. As a first step, ensure you have create a env file and added the Supabase project URL and anon key (example file here).

Setting up the Notifications Provider

The Notifications Provider ensures the app listens for incoming notifications, as well as define the behaviour when a user interacts with the notification. You can see the full implementation of the provider here.

Consuming the Provider

The next step is to consume the provider. Providers are typically added to the root of the application, otherwise known as the layout file here. This implementation:

  • Wraps your application with the notification provider

  • Ensures the app starts listening for notifications on launch

Permission Request Hook

We also need to create a hook which handles requesting notification permissions from the user (this only happens once for iOS devices, you'll need to re-install the app to prompt the user again). Refer to the hook implementation here.

UI Integration and Device Token Registration

The index file for the app is where everything pieces together. This includes:

  • Using the permission hook to request notification access

  • Retrieve and store the device token and notification preference in Supabase

  • Provides a toggle UI element to enable/disable notifications

You can find the implementation of the client UI here.

Testing the Implementation

Follow these steps to test your implementation locally:

1. Local Testing with Supabase CLI

Run your edge function locally:

supabase functions serve notifications-push --no-verify-jwt

This command starts your Edge Function locally without JWT verification, which is useful for development but should not be used in production.

2. Trigger the Function with cURL

Once your function is running locally, you can trigger it using cURL:

curl -i --location --request POST 'http://localhost:54321/functions/v1/send-notifications'

3. Verify Notification Delivery

When testing with a physical device with notifications enabled, you should see the notification appear on the device. If notifications aren't delivered, check your function logs for errors.

4. Testing the Toggle Feature

An important aspect to test is the notification toggle functionality:

  1. Toggle notifications off in your app's UI

  2. Run the edge function again

  3. Verify that no notification appears on your device

This confirms that your query is correctly filtering out devices where notifications have been disabled, respecting user preferences.

Next Steps

Now that you've built your basic push notification system, you can enhance it in several valuable ways:

  • Set up automated push notifications using Supabase's Edge Functions with cron jobs. This enables daily digests, weekly summaries, or time-sensitive campaigns without maintaining complex infrastructure.

  • Create a dedicated events table to track notification sends, deliveries, and opens

  • Target notifications based on user behavior or preferences

  • Add rate limiting to prevent notification fatigue

  • Set up logging and alerting for critical notification failures

These enhancements will transform your basic notification system into a sophisticated engagement tool that respects user preferences while maximising the impact of your communications.

Conclusion

By building a serverless push notification system with Supabase Edge Functions, you've created a cost-effective, maintainable, and scalable solution that gives you full control over your notification infrastructure. This approach eliminates third-party dependencies while maintaining high performance and reliability.

In our next technical guide, we'll explore implementing Android notifications using Firebase Cloud Messaging (FCM) within the same Supabase Edge Function architecture.

Want to skip the heavy lifting? Check out Launchtoday.dev - an Expo boilerplate designed to help you build apps faster. It’s packed with features such as authentication, payments, including push notifications, so you can focus on creating the best experience for your users without worrying about setup.

Introduction

Push notifications are the lifeblood of modern apps – they bring users back, deliver timely updates and increase engagement. However, here's the challenge - most developers default to third-party services like OneSignal or CleverTap which adds extra costs and dependencies to your project.

What if you could build your own serverless push notification system that you fully control?

This guide walks you through creating a complete push notification system using Supabase Edge Functions and Apple Push Notification Service (APNs) for iOS apps built with Expo. You'll gain full control over your notification infrastructure while keeping costs down and ensuring speedy delivery.

Note: This guide focuses on iOS integration. We'll cover Android/FCM implementation in a future article.

You can access the codebase used in this project here.

Prerequisites

Before diving in, make sure you have:

  • Expo experience: Familiarity with expo-notifications and handling permissions

  • Supabase knowledge: Understanding of Edge Functions creation, deployment, and execution using the Supabase CLI

  • Physical iOS device: The iOS simulator doesn't support push notifications

  • Active iOS Developer Account: Required for generating APNs authentication keys

Why Supabase for Push Notifications?

You might wonder why build on Supabase instead of using existing notification services. Here's why:

  • Complete control: Using Supabase allows full ownership of the notification infrastructure without depending on external services.

  • Automatic scaling: Edge Functions scale automatically with demand, eliminating the need to manage infrastructure scaling manually.

  • Performance optimisation: Edge Functions typically run in data centers closer to users' geographical locations, which can reduce latency for notification delivery.

  • Cost efficiency: The pay-per-execution model can be more cost-effective than subscription-based notification services, especially for applications with variable notification volumes.

System Overview

Our notification system consists of five key components:

  1. APNs Authentication: Secure connection to Apple's notification service

  2. Supabase Database: Storage for device tokens and user notification preferences

  3. Supabase Edge Function: Handler for push notification delivery

  4. Client Integration: Permission management and device token registration

  5. Trigger Mechanism: Event-based notification dispatching

Implementation Steps

1. Configuring APNs Authentication

To send iOS notifications, you first need to generate an APNs key:

  1. Go to App Store Connect > Certificates, Identifiers & Profiles

  2. Navigate to the Keys section and create a new key

  3. Enable Apple Push Notifications service

  4. Give your key a descriptive name and choose the appropriate environment:

    1. Sandbox for development

    2. Production for live apps

  5. Download the .p8 authentication key file (important: you can only download this once)

  6. Store these key details securely – you'll need them for your Edge Function

You can check out the following video for a step-by-step guide on creating your APNs key.

2. Supabase Database Schema Design

Next, let's set up a table in Supabase to store device tokens and notification preferences:

CREATE TABLE public.registered_devices (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    device_token VARCHAR NOT NULL,
    device_type VARCHAR NOT NULL,
    enabled_notifications BOOLEAN NOT NULL DEFAULT TRUE,
    UNIQUE(device_token)
)

This schema includes:

  • User reference with appropriate foreign key constraints

  • Device token for push notification addressing

  • Platform identification for multi-platform support

  • Toggle mechanisms for notification preferences

  • Timestamp fields for auditing and analytics

For proper security, implement row-level security (RLS) policies:

-- Enable RLS
ALTER TABLE public.registered_devices_example ENABLE ROW LEVEL

3. Supabase Edge Function Implementation

The heart of our system is the Edge Function that will:

  • Generate authentication tokens for APNs

  • Retrieve registered device tokens from the database

  • Format and send notifications to the appropriate endpoints

Environment Configuration

First, set up the necessary environment variables. Here's what you'll need (you can see the example env file here):

  • FUNCTIONS_SUPABASE_URL & FUNCTIONS_SUPABASE_SERVICE_ROLE_KEY: Found in your Supabase project under API settings.

  • APNS_AUTH_KEY_BASE64: Convert your .p8 authentication key downloaded earlier from Apple Developer to a Base64 string using the following command:

cat AuthKey_XXXXXXXXXX.p8 | base64
  • APNS_KEY_ID: The identifier shown when you created your APNs key



  • APNS_TEAM_ID: Found in your Apple Developer account homepage (a 10-character alphanumeric string):



  • APNS_BUNDLE_ID: Your app's bundle ID, which must match the registered app in your Apple Developer account:


Edge Function Entry Point

Next, define the main entry point of our Supabase Edge Function. This function will handle incoming requests, send a test notification, and return the result. You can find the example of the entry point here.

Sending Push Notifications to iOS devices

The core logic for issuing push notifications can be found here and includes the following:

  • Fetches registered devices in the registered_devices table we created earlier using the supabase syntax

  • Retrieves the Base64-encoded .p8 key from environment variables

  • Converts it into a cryptographic key using crypto.subtle.importKey

  • Generates a JSON Web Token (JWT) to authenticate with Apple Push Notification Service (APNs) - all of which can be found here

  • Sends the notification payload to all devices in parallel

  • Returns the response from APNs for each device - all of which can be found here

4. Client Integration

The following steps focus on the iOS Expo app which will receive the push notifications. As a first step, ensure you have create a env file and added the Supabase project URL and anon key (example file here).

Setting up the Notifications Provider

The Notifications Provider ensures the app listens for incoming notifications, as well as define the behaviour when a user interacts with the notification. You can see the full implementation of the provider here.

Consuming the Provider

The next step is to consume the provider. Providers are typically added to the root of the application, otherwise known as the layout file here. This implementation:

  • Wraps your application with the notification provider

  • Ensures the app starts listening for notifications on launch

Permission Request Hook

We also need to create a hook which handles requesting notification permissions from the user (this only happens once for iOS devices, you'll need to re-install the app to prompt the user again). Refer to the hook implementation here.

UI Integration and Device Token Registration

The index file for the app is where everything pieces together. This includes:

  • Using the permission hook to request notification access

  • Retrieve and store the device token and notification preference in Supabase

  • Provides a toggle UI element to enable/disable notifications

You can find the implementation of the client UI here.

Testing the Implementation

Follow these steps to test your implementation locally:

1. Local Testing with Supabase CLI

Run your edge function locally:

supabase functions serve notifications-push --no-verify-jwt

This command starts your Edge Function locally without JWT verification, which is useful for development but should not be used in production.

2. Trigger the Function with cURL

Once your function is running locally, you can trigger it using cURL:

curl -i --location --request POST 'http://localhost:54321/functions/v1/send-notifications'

3. Verify Notification Delivery

When testing with a physical device with notifications enabled, you should see the notification appear on the device. If notifications aren't delivered, check your function logs for errors.

4. Testing the Toggle Feature

An important aspect to test is the notification toggle functionality:

  1. Toggle notifications off in your app's UI

  2. Run the edge function again

  3. Verify that no notification appears on your device

This confirms that your query is correctly filtering out devices where notifications have been disabled, respecting user preferences.

Next Steps

Now that you've built your basic push notification system, you can enhance it in several valuable ways:

  • Set up automated push notifications using Supabase's Edge Functions with cron jobs. This enables daily digests, weekly summaries, or time-sensitive campaigns without maintaining complex infrastructure.

  • Create a dedicated events table to track notification sends, deliveries, and opens

  • Target notifications based on user behavior or preferences

  • Add rate limiting to prevent notification fatigue

  • Set up logging and alerting for critical notification failures

These enhancements will transform your basic notification system into a sophisticated engagement tool that respects user preferences while maximising the impact of your communications.

Conclusion

By building a serverless push notification system with Supabase Edge Functions, you've created a cost-effective, maintainable, and scalable solution that gives you full control over your notification infrastructure. This approach eliminates third-party dependencies while maintaining high performance and reliability.

In our next technical guide, we'll explore implementing Android notifications using Firebase Cloud Messaging (FCM) within the same Supabase Edge Function architecture.

Want to skip the heavy lifting? Check out Launchtoday.dev - an Expo boilerplate designed to help you build apps faster. It’s packed with features such as authentication, payments, including push notifications, so you can focus on creating the best experience for your users without worrying about setup.

Introduction

Push notifications are the lifeblood of modern apps – they bring users back, deliver timely updates and increase engagement. However, here's the challenge - most developers default to third-party services like OneSignal or CleverTap which adds extra costs and dependencies to your project.

What if you could build your own serverless push notification system that you fully control?

This guide walks you through creating a complete push notification system using Supabase Edge Functions and Apple Push Notification Service (APNs) for iOS apps built with Expo. You'll gain full control over your notification infrastructure while keeping costs down and ensuring speedy delivery.

Note: This guide focuses on iOS integration. We'll cover Android/FCM implementation in a future article.

You can access the codebase used in this project here.

Prerequisites

Before diving in, make sure you have:

  • Expo experience: Familiarity with expo-notifications and handling permissions

  • Supabase knowledge: Understanding of Edge Functions creation, deployment, and execution using the Supabase CLI

  • Physical iOS device: The iOS simulator doesn't support push notifications

  • Active iOS Developer Account: Required for generating APNs authentication keys

Why Supabase for Push Notifications?

You might wonder why build on Supabase instead of using existing notification services. Here's why:

  • Complete control: Using Supabase allows full ownership of the notification infrastructure without depending on external services.

  • Automatic scaling: Edge Functions scale automatically with demand, eliminating the need to manage infrastructure scaling manually.

  • Performance optimisation: Edge Functions typically run in data centers closer to users' geographical locations, which can reduce latency for notification delivery.

  • Cost efficiency: The pay-per-execution model can be more cost-effective than subscription-based notification services, especially for applications with variable notification volumes.

System Overview

Our notification system consists of five key components:

  1. APNs Authentication: Secure connection to Apple's notification service

  2. Supabase Database: Storage for device tokens and user notification preferences

  3. Supabase Edge Function: Handler for push notification delivery

  4. Client Integration: Permission management and device token registration

  5. Trigger Mechanism: Event-based notification dispatching

Implementation Steps

1. Configuring APNs Authentication

To send iOS notifications, you first need to generate an APNs key:

  1. Go to App Store Connect > Certificates, Identifiers & Profiles

  2. Navigate to the Keys section and create a new key

  3. Enable Apple Push Notifications service

  4. Give your key a descriptive name and choose the appropriate environment:

    1. Sandbox for development

    2. Production for live apps

  5. Download the .p8 authentication key file (important: you can only download this once)

  6. Store these key details securely – you'll need them for your Edge Function

You can check out the following video for a step-by-step guide on creating your APNs key.

2. Supabase Database Schema Design

Next, let's set up a table in Supabase to store device tokens and notification preferences:

CREATE TABLE public.registered_devices (
    id BIGSERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    device_token VARCHAR NOT NULL,
    device_type VARCHAR NOT NULL,
    enabled_notifications BOOLEAN NOT NULL DEFAULT TRUE,
    UNIQUE(device_token)
)

This schema includes:

  • User reference with appropriate foreign key constraints

  • Device token for push notification addressing

  • Platform identification for multi-platform support

  • Toggle mechanisms for notification preferences

  • Timestamp fields for auditing and analytics

For proper security, implement row-level security (RLS) policies:

-- Enable RLS
ALTER TABLE public.registered_devices_example ENABLE ROW LEVEL

3. Supabase Edge Function Implementation

The heart of our system is the Edge Function that will:

  • Generate authentication tokens for APNs

  • Retrieve registered device tokens from the database

  • Format and send notifications to the appropriate endpoints

Environment Configuration

First, set up the necessary environment variables. Here's what you'll need (you can see the example env file here):

  • FUNCTIONS_SUPABASE_URL & FUNCTIONS_SUPABASE_SERVICE_ROLE_KEY: Found in your Supabase project under API settings.

  • APNS_AUTH_KEY_BASE64: Convert your .p8 authentication key downloaded earlier from Apple Developer to a Base64 string using the following command:

cat AuthKey_XXXXXXXXXX.p8 | base64
  • APNS_KEY_ID: The identifier shown when you created your APNs key



  • APNS_TEAM_ID: Found in your Apple Developer account homepage (a 10-character alphanumeric string):



  • APNS_BUNDLE_ID: Your app's bundle ID, which must match the registered app in your Apple Developer account:


Edge Function Entry Point

Next, define the main entry point of our Supabase Edge Function. This function will handle incoming requests, send a test notification, and return the result. You can find the example of the entry point here.

Sending Push Notifications to iOS devices

The core logic for issuing push notifications can be found here and includes the following:

  • Fetches registered devices in the registered_devices table we created earlier using the supabase syntax

  • Retrieves the Base64-encoded .p8 key from environment variables

  • Converts it into a cryptographic key using crypto.subtle.importKey

  • Generates a JSON Web Token (JWT) to authenticate with Apple Push Notification Service (APNs) - all of which can be found here

  • Sends the notification payload to all devices in parallel

  • Returns the response from APNs for each device - all of which can be found here

4. Client Integration

The following steps focus on the iOS Expo app which will receive the push notifications. As a first step, ensure you have create a env file and added the Supabase project URL and anon key (example file here).

Setting up the Notifications Provider

The Notifications Provider ensures the app listens for incoming notifications, as well as define the behaviour when a user interacts with the notification. You can see the full implementation of the provider here.

Consuming the Provider

The next step is to consume the provider. Providers are typically added to the root of the application, otherwise known as the layout file here. This implementation:

  • Wraps your application with the notification provider

  • Ensures the app starts listening for notifications on launch

Permission Request Hook

We also need to create a hook which handles requesting notification permissions from the user (this only happens once for iOS devices, you'll need to re-install the app to prompt the user again). Refer to the hook implementation here.

UI Integration and Device Token Registration

The index file for the app is where everything pieces together. This includes:

  • Using the permission hook to request notification access

  • Retrieve and store the device token and notification preference in Supabase

  • Provides a toggle UI element to enable/disable notifications

You can find the implementation of the client UI here.

Testing the Implementation

Follow these steps to test your implementation locally:

1. Local Testing with Supabase CLI

Run your edge function locally:

supabase functions serve notifications-push --no-verify-jwt

This command starts your Edge Function locally without JWT verification, which is useful for development but should not be used in production.

2. Trigger the Function with cURL

Once your function is running locally, you can trigger it using cURL:

curl -i --location --request POST 'http://localhost:54321/functions/v1/send-notifications'

3. Verify Notification Delivery

When testing with a physical device with notifications enabled, you should see the notification appear on the device. If notifications aren't delivered, check your function logs for errors.

4. Testing the Toggle Feature

An important aspect to test is the notification toggle functionality:

  1. Toggle notifications off in your app's UI

  2. Run the edge function again

  3. Verify that no notification appears on your device

This confirms that your query is correctly filtering out devices where notifications have been disabled, respecting user preferences.

Next Steps

Now that you've built your basic push notification system, you can enhance it in several valuable ways:

  • Set up automated push notifications using Supabase's Edge Functions with cron jobs. This enables daily digests, weekly summaries, or time-sensitive campaigns without maintaining complex infrastructure.

  • Create a dedicated events table to track notification sends, deliveries, and opens

  • Target notifications based on user behavior or preferences

  • Add rate limiting to prevent notification fatigue

  • Set up logging and alerting for critical notification failures

These enhancements will transform your basic notification system into a sophisticated engagement tool that respects user preferences while maximising the impact of your communications.

Conclusion

By building a serverless push notification system with Supabase Edge Functions, you've created a cost-effective, maintainable, and scalable solution that gives you full control over your notification infrastructure. This approach eliminates third-party dependencies while maintaining high performance and reliability.

In our next technical guide, we'll explore implementing Android notifications using Firebase Cloud Messaging (FCM) within the same Supabase Edge Function architecture.

Want to skip the heavy lifting? Check out Launchtoday.dev - an Expo boilerplate designed to help you build apps faster. It’s packed with features such as authentication, payments, including push notifications, so you can focus on creating the best experience for your users without worrying about setup.