Send your first alert
Install Zona, create a source, and send an alert from your terminal with curl, PowerShell, Python or Node.js.
You need an iPhone with Zona installed and a terminal on any computer. By the end of this page an alert from that computer will be sitting in your inbox.
Install Zona and create a source
Link to this section- Install Zona on your iPhone and open it. Zona is currently a private TestFlight preview and coming to the App Store (availability).
- Go to Sources and create a source for the computer you are sitting at. Give it a name you
will recognise in the inbox, such as
Office PC. - Copy the token that starts with
zona_live_straight away.
Put the token in an environment variable
Link to this sectionKeep the token out of your scripts. For this walkthrough, set it for the current terminal session:
export ZONA_SOURCE_TOKEN='zona_live_YOUR_SOURCE_TOKEN'$env:ZONA_SOURCE_TOKEN = 'zona_live_YOUR_SOURCE_TOKEN'For anything that runs unattended, move the token into your OS secret store or your CI provider’s secrets. Authentication and source tokens covers the options.
Send the alert
Link to this sectionEvery request needs three headers: the token as a Bearer credential, an Idempotency-Key that
names this one event, and a Content-Type. title and body are the only required fields.
curl --request POST \
"https://gerncrjtrdjtjvybvseb.supabase.co/functions/v1/notify" \
--header "Authorization: Bearer $ZONA_SOURCE_TOKEN" \
--header "Idempotency-Key: quickstart-$(date +%s)" \
--header "Content-Type: application/json" \
--data '{
"title": "Hello from my terminal",
"body": "If you can read this, the source token works.",
"category": "test",
"severity": "low"
}'$headers = @{
Authorization = "Bearer $env:ZONA_SOURCE_TOKEN"
'Idempotency-Key' = 'quickstart-' + [guid]::NewGuid().ToString()
}
$payload = @{
title = 'Hello from my terminal'
body = 'If you can read this, the source token works.'
category = 'test'
severity = 'low'
} | ConvertTo-Json
Invoke-RestMethod `
-Method Post `
-Uri 'https://gerncrjtrdjtjvybvseb.supabase.co/functions/v1/notify' `
-Headers $headers `
-ContentType 'application/json' `
-Body $payload `
-TimeoutSec 10import os
import uuid
import requests # pip install requests
response = requests.post(
"https://gerncrjtrdjtjvybvseb.supabase.co/functions/v1/notify",
headers={
"Authorization": f"Bearer {os.environ['ZONA_SOURCE_TOKEN']}",
"Idempotency-Key": f"quickstart-{uuid.uuid4()}",
},
json={
"title": "Hello from my terminal",
"body": "If you can read this, the source token works.",
"category": "test",
"severity": "low",
},
timeout=10,
)
print(response.status_code, response.json())// Node.js 20 or later: fetch and crypto.randomUUID are built in.
const response = await fetch('https://gerncrjtrdjtjvybvseb.supabase.co/functions/v1/notify', {
method: 'POST',
headers: {
authorization: `Bearer ${process.env.ZONA_SOURCE_TOKEN}`,
'idempotency-key': `quickstart-${crypto.randomUUID()}`,
'content-type': 'application/json',
},
body: JSON.stringify({
title: 'Hello from my terminal',
body: 'If you can read this, the source token works.',
category: 'test',
severity: 'low',
}),
signal: AbortSignal.timeout(10_000),
});
console.log(response.status, await response.json());Save it as hello.mjs and run node hello.mjs, so top-level await works.
Read the response
Link to this sectionA new alert returns HTTP 202 with a body like this:
{
"notificationId": "87c4215a-03e3-4c96-af7c-e4043120a514",
"sourceId": "05c46ccb-0a9e-48c1-9b19-e0398f6ea69b",
"sourceName": "Office PC",
"acceptedAt": "2026-07-26T10:30:00.000Z",
"idempotentReplay": false,
"attachmentAccepted": false,
"attachmentError": null,
"pushAttempted": 1,
"pushAccepted": 0,
"pushQueued": 1
}notificationId is the inbox record. pushQueued counts the phone deliveries Zona has lined up.
It can be 0 if you are in quiet hours or have no phone registered for push, and the alert is
still in your inbox. pushAccepted is always 0 here because push results arrive after the
response.
Find it in the inbox
Link to this sectionOpen Zona. The alert is at the top of the inbox under the source name you chose, with a thin green
ring around the source’s avatar for low severity. While Zona is open, a new alert shows as a
banner at the top of the screen, or goes straight into the list if the inbox is already in view with
no search or filter applied. If no banner appeared, check that notifications are allowed for Zona and that quiet hours are off; the
alert stays in the inbox either way.
If it did not work
Link to this section401withINVALID_TOKEN: the token is missing, mistyped, paused, expired or revoked. Check that the environment variable is set in the same terminal, or create a new key in Sources.400withINVALID_IDEMPOTENCY_KEY: the key must be 8 to 128 characters of letters, digits,.,_,:and-, starting with a letter or digit.400withINVALID_PAYLOAD: the JSON is malformed or a field breaks a rule. On Windows, check that the quotes survived your shell.
Every status code is listed in Errors and limits.
Next steps
Link to this sectionBefore you wire this into a real job, read Idempotency and retries: a stable key per event is what keeps a retried request from sending the same alert twice. Then pick a ready-made integration from Recipes.