Event triggers
Run a function when a document is written, a row changes, a user signs up, a file is uploaded or a message is published.
A trigger runs one of your functions every time something happens in the project — no polling, no webhook endpoint to host. Delivery is at-least-once: failed runs are retried with backoff and, after the last try, kept as dead letters you can inspect and replay.
What can trigger a function
| Source | Events | Resource | The event carries |
|---|---|---|---|
db.document | document.created, document.updated, document.deleted | Collection | data.id, data.collection, data.document (the new document; null when deleted) |
db.row | row.inserted, row.updated, row.deleted | Table | data.table, data.operation, data.row_count — one event per statement; read the rows with SQL |
auth.user | user.created, user.deleted | — | data.user_id, data.email, data.provider |
storage.object | object.created, object.deleted | Bucket | data.bucket, data.path, data.size, data.content_type |
realtime.message | message.published | Channel | data.channel, data.payload |
Every event also has event_id, type, resource, subject (document id, object path or user id), actor (who caused it, when known) and occurred_at. Resource accepts wildcards (orders_*, *), and only subjects starting with narrows further (for example uploads/).
Create a trigger
sov functions triggers create send-receipt --on db.document -r orders --event document.created
sov functions triggers create resize-image --on storage.object -r avatars --prefix uploads/ --max-attempts 5
sov functions triggers list
sov functions triggers test <trigger-id> # queue one run with a sample eventimport os, httpx
def handler(event, context):
order = event["data"]["document"]
# Delivery is at-least-once: pass the idempotency key to anything with side effects.
httpx.post("https://api.resend.com/emails",
headers={"Authorization": f"Bearer {os.environ['RESEND_API_KEY']}",
"Idempotency-Key": context["idempotency_key"]},
json={"to": order["email"], "subject": "Thanks for your order", "text": f"Total: {order['total']}"})
return {"sent": True}Retries, dead letters and replay
- A run that raises, times out or can't start is retried after 2 s, 4 s, 8 s… (at most 10 minutes apart) until it has used the trigger's tries.
- Then it is a dead letter: Triggers & logs → Dead letters shows the event, every try's last error and its output. Fix the cause, then Replay (one or many): the same event runs again with the function's *current* code and a fresh set of tries.
- Each (trigger, event) pair runs once even if the event is delivered twice;
context.idempotency_keyis that pair, so use it for payments, emails and other side effects. - A trigger that receives more than 1,000 events a minute — usually a function writing to what triggers it — has the extra events dead-lettered with
THROTTLEDinstead of running. - Over your plan's monthly invocations (on plans that stop at the limit) a run is dead-lettered with
PLAN_LIMIT_REACHED; replay it after the limit resets or after upgrading.
sov functions dead-letters list
sov functions dead-letters replay <invocation-id> …
sov functions dead-letters replay --all --function send-receipt
sov functions dead-letters discard <invocation-id>Identity and security
A trigger run has no user session and no platform privileges: context["caller"] is {"role": "trigger"}. The function reaches your data with its own secrets (for example a service key you stored), under the same policies as any other request. Triggers only receive events of their own project and environment.
Plans
| Free | Pro | Ultra | Team | Enterprise | |
|---|---|---|---|---|---|
| Triggers per project | 2 | 25 | 100 | 250 | 1,000 |
| Tries per event | 3 | 8 | 10 | 12 | 15 |
| Dead letters kept | 1 day | 7 days | 14 days | 30 days | 30 days |
Each run counts as one invocation and its compute (GB-seconds), like a call. See Plan limits.