Messaging: push and email to your users
One API for push and email to your app users: templates with variables, audiences written in SQL, a preview before every send, marketing opt-outs with one-click unsubscribe, and a delivery log.
Messaging sends a template to an audience over push (your FCM / APNs credentials, see Push notifications) and/or email (Sovaria's sender or your own SMTP). Every recipient gets their own copy with their own variables, and every send leaves a per-recipient log.
Templates
A template has push content (title, body, image, data), email content (subject, html, text) or both, with {{ variables }} anywhere. Transactional templates (receipts, security alerts) always go out; marketing templates skip users who opted out and add an unsubscribe link and the one-click List-Unsubscribe header (RFC 8058) to every email.
await sov.api.messaging.putTemplate({
name: 'weekly-digest',
category: 'marketing',
push: { title: 'Hi {{ first_name }}', body: 'Your week on the {{ plan }} plan' },
email: { subject: 'Your week, {{ first_name }}', html: '<p>Hello {{ first_name }} …</p>', text: 'Hello {{ first_name }} …' },
})Audiences in SQL
An audience is a query on your project database that returns user_id (your app users' ids). Every other column becomes a variable for that user, so the data that picks the audience also personalises the message. Queries run read-only, as one statement, with a 10-second timeout; ids that aren't app users of the project are left out.
select p.user_id, p.first_name, p.plan, count(o.id) as orders
from profiles p left join orders o on o.user_id = p.user_id
where p.plan = 'pro' and p.last_seen_at > now() - interval '30 days'
group by 1, 2, 3Preview, then send
const preview = await sov.api.messaging.send({
template: 'weekly-digest',
to: { audience: 'active-pro' }, // or user_ids, sql, topic, all_users
variables: { promo: '20% off' }, // the same for everyone
dry_run: true,
})
// preview.counts → { push: { eligible: 980, no_device: 250, opted_out: 10 }, email: { eligible: 1190, … } }
// preview.preview → the first recipient's rendered push and email
const send = await sov.api.messaging.send({ template: 'weekly-digest', to: { audience: 'active-pro' }, variables: { promo: '20% off' } })
const done = await sov.api.messaging.getSend({ send_id: send.id }) // status: sending → done- A send answers at once and continues in the background, 500 recipients at a time; History in the console (or
getSend) shows the progress. - Each recipient and channel is logged as
sent,failed(with an error code such asUNREGISTERED,SEND_ERROR,PLAN_LIMIT),no_device,no_emailoropted_out— never the content. - Push goes to every device the user registered; a user counts as reached when any device accepts it.
- Email through Sovaria's sender is paced at your plan's emails per second instead of failing; your own SMTP (Authentication › Email) isn't counted against
email.sends_month. - Sends from functions use the service role key; app users and the anon key can never send.
Opt-outs and unsubscribe
// The signed-in user's own marketing choices
await sov.api.messaging.setPreferences({ marketing: { email: false } })
const { marketing } = await sov.api.messaging.getPreferences() // { push: true, email: false }Marketing emails carry a signed unsubscribe link that works without signing in (a button on a small page, so mail scanners opening the link don't unsubscribe anyone) and the one-click header mail apps show as "Unsubscribe". Your own settings screen can record choices with setUserOptout.
Limits
| Free | Pro | Ultra | Team | Enterprise | |
|---|---|---|---|---|---|
| Message templates per project | 10 | 100 | 300 | 1,000 | 5,000 |
| Message audiences per project | 3 | 50 | 200 | 500 | 2,000 |
| Recipients per message send | 1,000 | 50,000 | 200,000 | 1,000,000 | 5,000,000 |
| Message delivery log history | 7 days | 30 days | 90 days | 180 days | 365 days |
| Monthly push notifications | 10,000 | 1,000,000 | 5,000,000 | 20,000,000 | 100,000,000 |
| Monthly emails from Sovaria's sender (own SMTP is unlimited) | 200 | 2,000 | 6,000 | 30,000 | 150,000 |