OneSignal
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.
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.
Step 1: Getting started#
Before we dive into the code, this guide assumes that you have the following ready
- Supabase project created
- OneSignal app created
- Supabase CLI installed on your machine
Let’s create a Next.js app with tailwind CSS pre-installed
1npx create-next-app -e with-tailwindcss --ts
We will then install the Supabase and OneSignal SDK.
1npm i @supabase/supabase-js 2npm 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.
import { createClient, User } from '@supabase/supabase-js'
import type { NextPage } from 'next'
import Head from 'next/head'
import React, { useEffect, useState } from 'react'
import OneSignal from 'react-onesignal'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
const oneSignalAppId = process.env.NEXT_PUBLIC_ONESIGNAL_APP_ID!
const supabase = createClient(supabaseUrl, supabaseAnonKey)
const Home: NextPage = () => {
const [user, setUser] = useState<User | null>(null)
const [oneSignalInitialized, setOneSignalInitialized] = useState<boolean>(false)
/**
* Initializes OneSignal SDK for a given Supabase User ID
* @param uid Supabase User ID
*/
const initializeOneSignal = async (uid: string) => {
if (oneSignalInitialized) {
return
}
setOneSignalInitialized(true)
await OneSignal.init({
appId: oneSignalAppId,
notifyButton: {
enable: true,
},
allowLocalhostAsSecureOrigin: true,
})
await OneSignal.setExternalUserId(uid)
}
const sendMagicLink = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const { email } = Object.fromEntries(new FormData(event.currentTarget))
if (typeof email !== 'string') return
const { error } = await supabase.auth.signInWithOtp({ email })
if (error) {
alert(error.message)
} else {
alert('Check your email inbox')
}
}
// Place a order with the selected price
const submitOrder = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const { price } = Object.fromEntries(new FormData(event.currentTarget))
if (typeof price !== 'string') return
const { error } = await supabase.from('orders').insert({ price: Number(price) })
if (error) {
alert(error.message)
}
}
useEffect(() => {
const initialize = async () => {
const initialUser = (await supabase.auth.getUser())?.data.user
setUser(initialUser ?? null)
if (initialUser) {
initializeOneSignal(initialUser.id)
}
}
initialize()
const authListener = supabase.auth.onAuthStateChange(async (event, session) => {
const user = session?.user ?? null
setUser(user)
if (user) {
initializeOneSignal(user.id)
}
})
return () => {
authListener.data.subscription.unsubscribe()
}
}, [])
return (
<>
<Head>
<title>OneSignal Order Notification App</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="flex items-center justify-center min-h-screen bg-black">
{user ? (
<form className="flex flex-col space-y-2" onSubmit={submitOrder}>
<select
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded block p-2"
name="price"
>
<option value="100">$100</option>
<option value="200">$200</option>
<option value="300">$300</option>
</select>
<button type="submit" className="py-1 px-4 text-lg bg-green-400 rounded">
Place an Order
</button>
</form>
) : (
<form className="flex flex-col space-y-2" onSubmit={sendMagicLink}>
<input
className="border-green-300 border rounded p-2 bg-transparent text-white"
type="email"
name="email"
placeholder="Email"
/>
<button type="submit" className="py-1 px-4 text-lg bg-green-400 rounded">
Send Magic Link
</button>
</form>
)}
</main>
</>
)
}
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 are setting the Supabase user ID as an external user ID of OneSignal. This allows us to later send push notifications to the user using their Supabase user ID from the backend, which is very handy.
await OneSignal.setExternalUserId(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
1NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL 2NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY 3NEXT_PUBLIC_ONESIGNAL_APP_ID=YOUR_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.
1supabase functions new notify
Replace the contents of supabase/functions/notify/index.ts
with the following
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'
import * as OneSignal from 'https://esm.sh/@onesignal/node-onesignal@1.0.0-beta7'
const _OnesignalAppId_ = Deno.env.get('ONESIGNAL_APP_ID')!
const _OnesignalUserAuthKey_ = Deno.env.get('USER_AUTH_KEY')!
const _OnesignalRestApiKey_ = Deno.env.get('ONESIGNAL_REST_API_KEY')!
const configuration = OneSignal.createConfiguration({
userKey: _OnesignalUserAuthKey_,
appKey: _OnesignalRestApiKey_,
})
const onesignal = new OneSignal.DefaultApi(configuration)
serve(async (req) => {
try {
const { record } = await req.json()
// Build OneSignal notification object
const notification = new OneSignal.Notification()
notification.app_id = _OnesignalAppId_
notification.include_external_user_ids = [record.user_id]
notification.contents = {
en: `You just spent $${record.price}!`,
}
const onesignalApiRes = await onesignal.createNotification(notification)
return new Response(JSON.stringify({ onesignalResponse: onesignalApiRes }), {
headers: { 'Content-Type': 'application/json' },
})
} catch (err) {
console.error('Failed to create OneSignal notification', err)
return new Response('Server error.', {
headers: { 'Content-Type': 'application/json' },
status: 400,
})
}
})
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.
1ONESIGNAL_APP_ID=YOUR_ONESIGNAL_APP_ID 2USER_AUTH_KEY=YOUR_USER_AUTH_KEY 3ONESIGNAL_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.
Once your environment variables are filled in, you can run the following command to set the environment variable.
1supabase 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.
1supabase 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.
create table if not exists public.orders ( id uuid not null primary key default uuid_generate_v4 (), created_at timestamptz not null default now (), user_id uuid not null default auth.uid (), price int8 not null );
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.
const { error } = await supabase.from('orders').insert({
price: 100,
})
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.
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.
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.