HTTP API: messaging
Email and push campaigns to your app's users: templates, audiences defined by SQL, previews, sends, deliveries, usage and opt-outs.
Send push notifications and emails to your app's users: templates, SQL audiences, sends, the delivery log and opt-outs (Messaging: push and email to your users).
| Base URL | https://<project-ref>.sovarias.in — your project URL from Connect. |
| Keys | Send the project's key as apikey. Calls made for a signed-in user also send Authorization: Bearer <access token>. |
| Errors | A status code and {"detail": …} — a sentence, or {code, message} for errors your code may branch on (HTTP API). |
Templates
GET/api/v1/projects/{project_id}/messaging/templates
Message templates.
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
project_idstringrequired- The project.
Responses
- 200Success.
- 401No valid session or key.
curl -s "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/templates" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"templates": [
{
"name": "order-shipped",
"description": "Sent when an order ships",
"category": "transactional",
"push": {
"title": "Your order shipped",
"body": "Order #{{ order_id }} is on its way."
},
"email": {
"subject": "Order #{{ order_id }} shipped",
"html": "<p>Hi {{ name }}, your order is on its way.</p>",
"text": "Hi {{ name }}, your order is on its way."
},
"channels": [
"push",
"email"
],
"updated_by": "ana@acme.com",
"created_at": "2026-10-08T10:00:00+00:00",
"updated_at": "2026-10-12T09:00:00+00:00"
}
]
}GET/api/v1/projects/{project_id}/messaging/templates/{name}
One template.
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
namestringrequired- The template.
project_idstringrequired- The project.
Responses
- 200Success.
- 404No such template.
- 401No valid session or key.
curl -s "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/templates/order-shipped" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"name": "order-shipped",
"description": "Sent when an order ships",
"category": "transactional",
"push": {
"title": "Your order shipped",
"body": "Order #{{ order_id }} is on its way."
},
"email": {
"subject": "Order #{{ order_id }} shipped",
"html": "<p>Hi {{ name }}, your order is on its way.</p>",
"text": "Hi {{ name }}, your order is on its way."
},
"channels": [
"push",
"email"
],
"updated_by": "ana@acme.com",
"created_at": "2026-10-08T10:00:00+00:00",
"updated_at": "2026-10-12T09:00:00+00:00"
}PUT/api/v1/projects/{project_id}/messaging/templates/{name}
Create or replace a template with push and/or email content.
{{ variables }} are filled per user from the send's variables and the audience's extra columns. Marketing messages respect each user's opt-out and email carries a one-click unsubscribe; transactional ones (receipts, account notices) don't. Names: lowercase letters, digits, _, -. The plan limits how many (msg.templates_per_project) (Messaging: push and email to your users).
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
namestringrequired- Template name.
project_idstringrequired- The project.
Body
descriptionstringoptionalcategorystringoptionaldefaulttransactional- One of
transactional,marketing pushobjectoptional{title, body, image?, data?, sound?, badge?}.emailobjectoptional{subject, html?, text?}.
Responses
- 200Success.
- 422A template needs push or email content (or a field is invalid).
- 403
PLAN_LIMIT_REACHED. - 401No valid session or key.
curl -s -X PUT "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/templates/order-shipped" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"description": "Sent when an order ships", "category": "transactional", "push": {"title": "Your order shipped", "body": "Order #{{ order_id }} is on its way."}, "email": {"subject": "Order #{{ order_id }} shipped", "html": "<p>Hi {{ name }}, your order is on its way.</p>", "text": "Hi {{ name }}, your order is on its way."}}'Response
{
"name": "order-shipped",
"description": "Sent when an order ships",
"category": "transactional",
"push": {
"title": "Your order shipped",
"body": "Order #{{ order_id }} is on its way."
},
"email": {
"subject": "Order #{{ order_id }} shipped",
"html": "<p>Hi {{ name }}, your order is on its way.</p>",
"text": "Hi {{ name }}, your order is on its way."
},
"channels": [
"push",
"email"
],
"updated_by": "ana@acme.com",
"created_at": "2026-10-08T10:00:00+00:00",
"updated_at": "2026-10-12T09:00:00+00:00"
}DELETE/api/v1/projects/{project_id}/messaging/templates/{name}
Delete a template.
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
namestringrequired- The template.
project_idstringrequired- The project.
Responses
- 204Success, with no body.
- 401No valid session or key.
curl -s -X DELETE "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/templates/order-shipped" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Audiences
GET/api/v1/projects/{project_id}/messaging/audiences
Saved audiences (SQL that selects users).
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
project_idstringrequired- The project.
Responses
- 200Success.
- 401No valid session or key.
curl -s "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/audiences" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"audiences": [
{
"name": "inactive-30d",
"description": "No order in 30 days",
"sql": "select user_id, name from customers where last_order_at < now() - interval '30 days'",
"last_count": 412,
"last_run_at": "2026-10-18T08:00:00+00:00",
"updated_by": "ana@acme.com",
"updated_at": "2026-10-15T09:00:00+00:00"
}
]
}GET/api/v1/projects/{project_id}/messaging/audiences/{name}
One audience.
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
namestringrequired- The audience.
project_idstringrequired- The project.
Responses
- 200Success.
- 401No valid session or key.
curl -s "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/audiences/inactive-30d" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"name": "inactive-30d",
"description": "No order in 30 days",
"sql": "select user_id, name from customers where last_order_at < now() - interval '30 days'",
"last_count": 412,
"last_run_at": "2026-10-18T08:00:00+00:00",
"updated_by": "ana@acme.com",
"updated_at": "2026-10-15T09:00:00+00:00"
}PUT/api/v1/projects/{project_id}/messaging/audiences/{name}
Save an audience: a SELECT that returns user_id (other columns become variables).
Runs read-only with a 10-second limit, up to 20,000 characters of SQL. Rows whose user_id isn't an app user of the project are skipped.
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
namestringrequired- Audience name.
project_idstringrequired- The project.
Body
sqlstringrequired- The query.
- 1–20,000 characters
descriptionstring | nulloptional- A note.
- 0–300 characters
Responses
- 200Success.
- 422Not a SELECT (or WITH … SELECT), no
user_idcolumn, or the query failed. - 403
PLAN_LIMIT_REACHED. - 401No valid session or key.
curl -s -X PUT "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/audiences/inactive-30d" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"sql": "select user_id, name from customers where last_order_at < now() - interval '30 days'", "description": "No order in 30 days"}'Response
{
"name": "inactive-30d",
"description": "No order in 30 days",
"sql": "select user_id, name from customers where last_order_at < now() - interval '30 days'",
"last_count": 412,
"last_run_at": "2026-10-18T08:00:00+00:00",
"updated_by": "ana@acme.com",
"updated_at": "2026-10-15T09:00:00+00:00"
}DELETE/api/v1/projects/{project_id}/messaging/audiences/{name}
Delete an audience.
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
namestringrequired- The audience.
project_idstringrequired- The project.
Responses
- 204Success, with no body.
- 401No valid session or key.
curl -s -X DELETE "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/audiences/inactive-30d" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"POST/api/v1/projects/{project_id}/messaging/audiences/preview
Try audience SQL: how many users it matches and a sample.
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
project_idstringrequired- The project.
Body
sqlstringrequired- The query.
- 1–20,000 characters
Returns
usersinteger- Rows that are app users of the project.
over_limit, limitboolean, integer- More rows than one send may reach (
msg.max_recipients_per_send). samplearray<object>- Up to 10 matching rows.
Responses
- 200Success.
- 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
curl -s -X POST "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/audiences/preview" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"sql": "select user_id, name from customers where last_order_at < now() - interval '30 days'"}'Response
{
"columns": [
"user_id",
"name"
],
"rows": 412,
"users": 409,
"over_limit": false,
"limit": 50000,
"not_app_users": 3,
"sample": [
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"name": "Ana"
}
]
}POST/api/v1/projects/{project_id}/messaging/audiences/{name}/preview
Preview a saved audience.
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
namestringrequired- The audience.
project_idstringrequired- The project.
Responses
- 200Success.
- 401No valid session or key.
curl -s -X POST "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/audiences/inactive-30d/preview" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"columns": [
"user_id",
"name"
],
"rows": 412,
"users": 409,
"over_limit": false,
"limit": 50000,
"not_app_users": 3,
"sample": [
{
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"name": "Ana"
}
]
}Sending and delivery
POST/api/v1/projects/{project_id}/messaging/send
Send push and/or email to app users. Answers at once (202); the send continues in the background.
Use a template, or content directly. dry_run: true counts recipients and renders a preview without sending. Recipients per send and monthly sends are limited by the plan (msg.max_recipients_per_send, push.sends_month, email.sends_month).
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
project_idstringrequired- The project.
Body
templatestringoptional- A template name (or give
content). contentobjectoptional{push?, email?}like a template's.toobjectrequired- One of
user_ids(array),audience(name),sql,topic(push topic) orall_users: true. channelsarray<string>optionalpush,email(default: every channel the content has).variablesobjectoptional- Values for
{{ variables }}. categorystringoptional- One of
transactional,marketing dry_runbooleanoptional
Returns
statusstringsending, thendoneorfailed.countsobject- Per channel:
eligible,no_device/no_email,opted_out, then delivery results (sent,failed).
Responses
- 202Accepted — the work continues in the background.
- 404No such template or audience.
- 409
PUSH_NOT_CONFIGURED— no FCM/APNs credentials. - 403
MESSAGING_DISABLED/PUSH_DISABLEDby support, or a plan limit. - 422No content, no
to, more than 10,000user_ids, bad variables (at most 50; text, numbers or booleans), or channels the content doesn't have. - 401No valid session or key.
curl -s -X POST "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/send" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"template": "order-shipped", "to": {"user_ids": ["3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93"]}, "variables": {"order_id": "1042", "name": "Ana"}}'Response
{
"id": "5d2c…",
"status": "sending",
"template": "order-shipped",
"audience": null,
"target": "users",
"channels": [
"push",
"email"
],
"category": "transactional",
"recipients": 1,
"counts": {
"push": {
"eligible": 1,
"no_device": 0,
"opted_out": 0
},
"email": {
"eligible": 1,
"no_email": 0,
"opted_out": 0
}
},
"dry_run": false,
"error": null,
"actor": "ana@acme.com",
"created_at": "2026-10-18T10:00:00+00:00",
"finished_at": null,
"not_app_users": 0
}GET/api/v1/projects/{project_id}/messaging/sends
Recent sends.
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
project_idstringrequired- The project.
Query parameters
limitintegeroptionaldefault50- How many (1–200).
- 1–200
Responses
- 200Success.
- 401No valid session or key.
curl -s "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/sends" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"sends": [
{
"id": "5d2c…",
"status": "done",
"template": "order-shipped",
"audience": null,
"target": "users",
"channels": [
"push",
"email"
],
"category": "transactional",
"recipients": 1,
"counts": {
"push": {
"eligible": 1,
"no_device": 0,
"opted_out": 0,
"sent": 1
},
"email": {
"eligible": 1,
"no_email": 0,
"opted_out": 0,
"sent": 1
}
},
"dry_run": false,
"error": null,
"actor": "ana@acme.com",
"created_at": "2026-10-18T10:00:00+00:00",
"finished_at": "2026-10-18T10:00:04+00:00"
}
]
}GET/api/v1/projects/{project_id}/messaging/sends/{send_id}
One send and its counts.
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
send_idstringrequired- The send.
project_idstringrequired- The project.
Responses
- 200Success.
- 404No such send.
- 401No valid session or key.
curl -s "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/sends/5d2c…" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"id": "5d2c…",
"status": "done",
"template": "order-shipped",
"audience": null,
"target": "users",
"channels": [
"push",
"email"
],
"category": "transactional",
"recipients": 1,
"counts": {
"push": {
"eligible": 1,
"no_device": 0,
"opted_out": 0
},
"email": {
"eligible": 1,
"no_email": 0,
"opted_out": 0
}
},
"dry_run": false,
"error": null,
"actor": "ana@acme.com",
"created_at": "2026-10-18T10:00:00+00:00",
"finished_at": "2026-10-18T10:00:04+00:00"
}GET/api/v1/projects/{project_id}/messaging/deliveries
Per-user delivery log.
Kept for the plan's msg.delivery_log_days.
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
project_idstringrequired- The project.
Query parameters
send_idstring | nulloptional- One send.
user_idstring | nulloptional- One user.
statusstring | nulloptionalsent,failed,opted_out,no_device,no_email.- 0–16 characters
limitintegeroptionaldefault100- How many (1–500).
- 1–500
offsetintegeroptionaldefault0- How many to skip.
- 0–1000000
Responses
- 200Success.
- 401No valid session or key.
curl -s "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/deliveries?send_id=5d2c…" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"deliveries": [
{
"send_id": "5d2c…",
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"channel": "push",
"status": "sent",
"error": null,
"at": "2026-10-18T10:00:02+00:00"
},
{
"send_id": "5d2c…",
"user_id": "3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93",
"channel": "email",
"status": "sent",
"error": null,
"at": "2026-10-18T10:00:03+00:00"
}
]
}GET/api/v1/projects/{project_id}/messaging/usage
This month's sends and the plan's messaging limits.
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
project_idstringrequired- The project.
Responses
- 200Success.
- 401No valid session or key.
curl -s "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/usage" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"this_month": {
"sends": 214,
"push_sent": 18420,
"email_sent": 3902
},
"limits": {
"msg.templates_per_project": 100,
"msg.audiences_per_project": 50,
"msg.max_recipients_per_send": 50000,
"msg.delivery_log_days": 30,
"push.sends_month": 1000000,
"email.sends_month": 2000
}
}Opt-outs
PUT/api/v1/projects/{project_id}/messaging/users/{user_id}/optouts
Opt a user in or out of marketing messages (for example from your own settings screen or support).
- Who
- A project owner or admin, or the service-role key (for example from a function).
Path parameters
user_idstringrequired- The user.
project_idstringrequired- The project.
Body
channelstringrequired- One of
push,email,all opted_outbooleanrequired
Returns
marketingobject- Channel → receives marketing messages.
Responses
- 200Success.
- 404Not an app user of the project.
- 401No valid session or key.
curl -s -X PUT "$URL/api/v1/projects/f0c3e9a2-1b4d-4e7f-8a6c-2d9b5e3f1a07/messaging/users/3c8f1e2a-9b47-4d6e-a5c1-7f0e2d4b8a93/optouts" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"channel": "email", "opted_out": true}'Response
{
"marketing": {
"push": true,
"email": false
}
}GET/api/v1/messaging/preferences
The signed-in user's marketing preferences.
- Who
- A signed-in app user:
Authorization: Bearer <access token>, plus the project'sapikey.
Responses
- 200Success.
- 401No valid session or key.
curl -s "$URL/api/v1/messaging/preferences" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN"Response
{
"marketing": {
"push": true,
"email": false
}
}PUT/api/v1/messaging/preferences
The signed-in user turns marketing push or email on or off.
- Who
- A signed-in app user:
Authorization: Bearer <access token>, plus the project'sapikey.
Body
marketingobject<string, boolean>required{push?, email?}→ true/false.
Responses
- 200Success.
- 401No valid session or key.
- 422The body is missing a required field or has a field of the wrong type.
curl -s -X PUT "$URL/api/v1/messaging/preferences" \
-H "apikey: $ANON_KEY" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"marketing": {"email": false}}'Response
{
"marketing": {
"push": true,
"email": false
}
}GET/api/v1/messaging/unsubscribe
The unsubscribe page linked from marketing emails (shows a button; mail scanners don't unsubscribe anyone).
- Who
- PUBLIC (a signed link).
Query parameters
pstringrequired- Project (from the link).
ustringrequired- User (from the link).
cstringoptionaldefaultemail- Channel (default
email). tstringrequired- Signature.
Responses
- 200An HTML page with an Unsubscribe button.
- 400The link is invalid.
curl -s "$URL/api/v1/messaging/unsubscribe" \
-H "apikey: $ANON_KEY"POST/api/v1/messaging/unsubscribe
One-click unsubscribe (List-Unsubscribe-Post, RFC 8058) and the page's button.
- Who
- PUBLIC (a signed link).
Query parameters
pstringrequired- Project.
ustringrequired- User.
cstringoptionaldefaultemail- Channel.
tstringrequired- Signature.
Responses
- 200An HTML confirmation page.
- 400The link is invalid.
curl -s -X POST "$URL/api/v1/messaging/unsubscribe" \
-H "apikey: $ANON_KEY"