Sending notifications
From SQL
In a trigger, a function, or the SQL editor:
select push.send('{"title": "Your order shipped", "body": "It arrives Thursday."}',
user_ids => array['3f1c...'::uuid]);
It returns the message's id. Because it is SQL, a notification can follow a change to your data directly. For example, telling a buyer their order shipped:
create function notify_shipped() returns trigger language plpgsql security definer as $$
begin
perform push.send(
jsonb_build_object('title', 'Your order shipped', 'data', jsonb_build_object('order', new.id)),
user_ids => array[new.buyer_id]);
return new;
end $$;
create trigger order_shipped after update of status on orders
for each row when (new.status = 'shipped' and old.status is distinct from 'shipped')
execute function notify_shipped();
The notification is queued with the transaction and sent once it commits: a rolled-back update sends nothing.
From a server
With the service_role key:
curl -X POST "https://<ref>.api.snoutdata.com/push/v1/send" \
-H "apikey: <your service_role key>" \
-H "Content-Type: application/json" \
-d '{"notification": {"title": "Your order shipped"}, "user_ids": ["3f1c..."]}'
or with @snoutdata/client:
const { data, error } = await db.push.send({ notification: { title: 'Your order shipped' }, userIds: [userId] })
Who it goes to
A message has exactly one target:
| Target | Reaches |
|---|---|
user_ids | every device each of those users has registered |
topic | every device of every member of the topic |
device_ids | those devices only |
When, and how urgently
| Option | Meaning |
|---|---|
send_at | send later, at this time (paid plans; refused on free with a sentence saying why) |
ttl | seconds a provider may hold the notification for a device that is offline; after that it is dropped |
priority | high (the default: shown at once, may wake the device) or normal (the providers may batch it to save battery) |
collapse_key | a newer notification with the same key replaces an older one not yet delivered ("3 new messages" rather than three) |
From SQL they are named arguments: push.send(..., send_at => now() + interval '1 hour', priority => 'high').
What a notification can carry
| Field | Meaning |
|---|---|
title, body | the text shown |
data | your own values, delivered to your app beside the notification (as strings on Android) |
badge | the number on the app's icon (Apple, and Android launchers that show one) |
sound | "default", or a sound bundled in your app |
thread | groups notifications on the device |
image | a picture shown with it |
url | where a click on a web notification goes |
background | shows nothing and wakes your app to handle data; never sent to a browser, since browsers require every push to show something |
Per platform. For anything a platform has that this shape does not name, an apns, fcm or
web object is merged over what is built for that platform, and ignored by the others: apns over
the payload Apple reads (its aps and your data), fcm over FCM's message, and web over what
the service worker receives:
select push.send('{"title": "Heads up", "apns": {"aps": {"interruption-level": "time-sensitive"}},
"fcm": {"android": {"notification": {"color": "#FF6600"}}}}',
user_ids => array['3f1c...'::uuid]);
An unknown top-level field is refused, not ignored, so a typo does not silently send less than you
meant. A notification that is too large for a platform once built (APNs and FCM take 4 KB) is
refused for that device with the reason.
Letting your users send
With no policy only service_role sends. Sending is an insert into push.messages run as the
caller, so a row-level security policy opens exactly what you mean. For example, letting a user
notify the other members of their chats:
create policy "notify my chats" on push.messages for insert to authenticated
with check (target_topic in (select 'chat:' || chat_id from chat_members where user_id = auth.uid()));
A sender cannot pretend to be somebody else: created_by is always the caller.
Topics
A topic is a named audience. Make one as the project's owner or with service_role:
insert into push.topics (name, description) values ('news', 'Product news');
A signed-in user joins and leaves for themselves:
curl -X PUT "https://<ref>.api.snoutdata.com/push/v1/topics/news/members" \
-H "apikey: <your anon key>" -H "Authorization: Bearer <the user's access token>"
(DELETE to leave; db.push.join('news') and db.push.leave('news') with the client). A message
sent with "topic": "news" then reaches every device of every member. Topic names are letters,
digits and . _ : / -, so chat:42 works as a per-conversation topic.