Launch Week 12: Day 3

Learn more
Back
OneSignal

OneSignal

OneSignal
OneSignal

Overview

OneSignal is a tool that allows you to send messages across different channels such as the following to keep your users engaged.

  • Push notifications
  • SMS
  • Emails
  • In-app notifications

Combined with Supabase Database Webhooks and you can provide realtime cloud messaging experience to your users.

You can get started with OneSignal and Supabase here.

Documentation

OneSignal is a tool that allows you to send messages across different channels such as the following to keep your users engaged.

  • Push notifications
  • SMS
  • Emails
  • In-app notifications

Here is William giving us the overview of how OneSignal can work with Supabase to send notifications to your users.

In this guide, we will build a similar app and steps you through how you can integrate OneSignal with Supabase to create a seamless cloud messaging experience for your users using Database webhooks and edge functions through a simple Next.js application.

Entity Diagram

We will create a simple ordering app and use Supabase Database Webhooks in conjunction with Edge Function to provide a real-time push notification experience.

You can find the complete example app along with the edge functions code to send the notifications here.

Ordering app UI

Step 1: Getting started

Before we dive into the code, this guide assumes that you have the following ready

Let’s create a Next.js app with tailwind CSS pre-installed


_10
npx create-next-app -e with-tailwindcss --ts

We will then install the Supabase and OneSignal SDK.


_10
npm i @supabase/supabase-js
_10
npm i react-onesignal

After that, follow the instructions here to set up OneSignal for the web. You can set the URL of the app as a local host if you want to run the app locally, or add a remote URL if you want to deploy your app to a public hosting. You should add the file you obtain in step 4 of the instruction under the public directory of your Next.js app like this.

Step 2: Build Next.js app

The Next.js app will have a login form for the user to sign in, and a button that they can press to make an order once they are signed in. Update the index.tsx file to the following.

pages/index.tsx

_139
import { createClient, User } from '@supabase/supabase-js'
_139
import type { NextPage } from 'next'
_139
import Head from 'next/head'
_139
import React, { useEffect, useState } from 'react'
_139
import OneSignal from 'react-onesignal'
_139
_139
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
_139
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
_139
const oneSignalAppId = process.env.NEXT_PUBLIC_ONESIGNAL_APP_ID!
_139
_139
const supabase = createClient(supabaseUrl, supabaseAnonKey)
_139
_139
const Home: NextPage = () => {
_139
const [user, setUser] = useState<User | null>(null)
_139
_139
const [oneSignalInitialized, setOneSignalInitialized] =
_139
useState<boolean>(false)
_139
_139
/**
_139
* Initializes OneSignal SDK for a given Supabase User ID
_139
* @param uid Supabase User ID
_139
*/
_139
const initializeOneSignal = async (uid: string) => {
_139
if (oneSignalInitialized) {
_139
return
_139
}
_139
setOneSignalInitialized(true)
_139
await OneSignal.init({
_139
appId: oneSignalAppId,
_139
notifyButton: {
_139
enable: true,
_139
},
_139
_139
allowLocalhostAsSecureOrigin: true,
_139
})
_139
_139
await OneSignal.login(uid)
_139
}
_139
_139
const sendMagicLink = async (event: React.FormEvent<HTMLFormElement>) => {
_139
event.preventDefault()
_139
const { email } = Object.fromEntries(new FormData(event.currentTarget))
_139
if (typeof email !== 'string') return
_139
_139
const { error } = await supabase.auth.signInWithOtp({ email })
_139
if (error) {
_139
alert(error.message)
_139
} else {
_139
alert('Check your email inbox')
_139
}
_139
}
_139
_139
// Place a order with the selected price
_139
const submitOrder = async (event: React.FormEvent<HTMLFormElement>) => {
_139
event.preventDefault()
_139
const { price } = Object.fromEntries(new FormData(event.currentTarget))
_139
if (typeof price !== 'string') return
_139
_139
const { error } = await supabase
_139
.from('orders')
_139
.insert({ price: Number(price) })
_139
if (error) {
_139
alert(error.message)
_139
}
_139
}
_139
_139
useEffect(() => {
_139
const initialize = async () => {
_139
const initialUser = (await supabase.auth.getUser())?.data.user
_139
setUser(initialUser ?? null)
_139
if (initialUser) {
_139
initializeOneSignal(initialUser.id)
_139
}
_139
}
_139
_139
initialize()
_139
_139
const authListener = supabase.auth.onAuthStateChange(
_139
async (event, session) => {
_139
const user = session?.user ?? null
_139
setUser(user)
_139
if (user) {
_139
initializeOneSignal(user.id)
_139
}
_139
}
_139
)
_139
_139
return () => {
_139
authListener.data.subscription.unsubscribe()
_139
}
_139
}, [])
_139
_139
return (
_139
<>
_139
<Head>
_139
<title>OneSignal Order Notification App</title>
_139
<link rel="icon" href="/favicon.ico" />
_139
</Head>
_139
_139
<main className="flex items-center justify-center min-h-screen bg-black">
_139
{user ? (
_139
<form className="flex flex-col space-y-2" onSubmit={submitOrder}>
_139
<select
_139
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded block p-2"
_139
name="price"
_139
>
_139
<option value="100">$100</option>
_139
<option value="200">$200</option>
_139
<option value="300">$300</option>
_139
</select>
_139
<button
_139
type="submit"
_139
className="py-1 px-4 text-lg bg-green-400 rounded"
_139
>
_139
Place an Order
_139
</button>
_139
</form>
_139
) : (
_139
<form className="flex flex-col space-y-2" onSubmit={sendMagicLink}>
_139
<input
_139
className="border-green-300 border rounded p-2 bg-transparent text-white"
_139
type="email"
_139
name="email"
_139
placeholder="Email"
_139
/>
_139
<button
_139
type="submit"
_139
className="py-1 px-4 text-lg bg-green-400 rounded"
_139
>
_139
Send Magic Link
_139
</button>
_139
</form>
_139
)}
_139
</main>
_139
</>
_139
)
_139
}
_139
_139
export default Home

There is quite a bit of stuff going on here, but basically, it’s creating a simple UI for the user to sign in using the magic link, and once the user is signed in, will initialize OneSignal to ask the user to receive notifications on the website.

Notice that inside the initializeOneSignal() function, we use the Supabase user ID a to log the user into OneSignal. This allows us to later send push notifications to the user using their Supabase user ID from the backend, which is very handy.


_10
await OneSignal.login(uid)

The front-end side of things is done here. Let’s get into the backend.

We also need to set our environment variables. Create a .env.local file and use the following template to set the environment variables. You can find your Supabase configuration in your dashboard under settings > API, and you can find the OneSignal app ID from Settings > Keys & IDs


_10
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
_10
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
_10
NEXT_PUBLIC_ONESIGNAL_APP_ID=YOUR_ONESIGNAL_APP_ID

Where to find OneSignal App ID

Step 3: Create the Edge Function

Let’s create an edge function that will receive database webhooks from the database and calls the OneSignal API to send the push notification.


_10
supabase functions new notify

Replace the contents of supabase/functions/notify/index.ts with the following


_40
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'
_40
import * as OneSignal from 'https://esm.sh/@onesignal/node-onesignal@1.0.0-beta7'
_40
_40
const _OnesignalAppId_ = Deno.env.get('ONESIGNAL_APP_ID')!
_40
const _OnesignalUserAuthKey_ = Deno.env.get('USER_AUTH_KEY')!
_40
const _OnesignalRestApiKey_ = Deno.env.get('ONESIGNAL_REST_API_KEY')!
_40
const configuration = OneSignal.createConfiguration({
_40
userKey: _OnesignalUserAuthKey_,
_40
appKey: _OnesignalRestApiKey_,
_40
})
_40
_40
const onesignal = new OneSignal.DefaultApi(configuration)
_40
_40
serve(async (req) => {
_40
try {
_40
const { record } = await req.json()
_40
_40
// Build OneSignal notification object
_40
const notification = new OneSignal.Notification()
_40
notification.app_id = _OnesignalAppId_
_40
notification.include_external_user_ids = [record.user_id]
_40
notification.contents = {
_40
en: `You just spent $${record.price}!`,
_40
}
_40
const onesignalApiRes = await onesignal.createNotification(notification)
_40
_40
return new Response(
_40
JSON.stringify({ onesignalResponse: onesignalApiRes }),
_40
{
_40
headers: { 'Content-Type': 'application/json' },
_40
}
_40
)
_40
} catch (err) {
_40
console.error('Failed to create OneSignal notification', err)
_40
return new Response('Server error.', {
_40
headers: { 'Content-Type': 'application/json' },
_40
status: 400,
_40
})
_40
}
_40
})

If you see bunch of errors in your editor, it's because your editor is not configured to use Deno. Follow the official setup guide here to setup your IDE to use Deno.

The function receives a record object, which is the row inserted in your orders table, and constructs a notification object to then send to OneSignal to deliver the push notification.

We also need to set the environment variable for the function. Create a .env file under your supabase directory and paste the following.


_10
ONESIGNAL_APP_ID=YOUR_ONESIGNAL_APP_ID
_10
USER_AUTH_KEY=YOUR_USER_AUTH_KEY
_10
ONESIGNAL_REST_API_KEY=YOUR_ONESIGNAL_REST_API_KEY

ONESIGNAL_APP_ID and ONESIGNAL_REST_API_KEY can be found under Settings > Keys & IDs of your OneSignal app, and USER_AUTH_KEY can be found by going to Account & API Keys page by clicking your icon in the top right corner and scrolling to the User Auth Key section.

Where to find OneSignal User Auth Key

Once your environment variables are filled in, you can run the following command to set the environment variable.


_10
supabase secrets set --env-file ./supabase/.env

At this point, the function should be ready to be deployed! Run the following command to deploy your functions to the edge! The no-verify-jwt flag is required if you plan to call the function from a webhook.


_10
supabase functions deploy notify --no-verify-jwt

Step 4: Setting up the Supabase database

Finally, we get to set up the database! Run the following SQL to set up the orders table.


_10
create table
_10
if not exists public.orders (
_10
id uuid not null primary key default uuid_generate_v4 (),
_10
created_at timestamptz not null default now (),
_10
user_id uuid not null default auth.uid (),
_10
price int8 not null
_10
);

As you can see, the orders table has 4 columns and 3 of them have default values. That means all we need to send from the front-end app is the price. That is why our insert statement looked very simple.


_10
const { error } = await supabase.from('orders').insert({
_10
price: 100,
_10
})

Let’s also set up the webhook so that whenever a new row is inserted in the orders table, it calls the edge function. Go to Database > Webhooks and create a new Database Webhook. The table should be set to orders and Events should be inserted. The type should be HTTP Request, the HTTP method should be POST, and the URL should be the URL of your edge function. Hit confirm to save the webhook configuration.

Supabase Webhooks configuration

At this point, the app should be complete! Run your app locally with npm run dev, or deploy your app to a hosting service and see how you receive a push notification when you place an order! Remember that if you decide to deploy your app to a hosting service, you would need to create another OneSignal app configured for your local address.

Ordering app UI

Resources

This particular example was using Next.js, but you can apply the same principles to implement send push notification, SMS, Emails, and in-app-notifications on other platforms as well.

Details

DeveloperOneSignal
CategoryMessaging
DocumentationLearn

Third-party integrations and docs are managed by Supabase partners.