Docs

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.

Updated Oct 17, 2026Open in console

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.

runfails every tryA changedocument, row,user, file,messageEvent streamdurable, orderedper projectQueued runone per trigger,deduplicatedYour functionits own keys, nouser sessionDead lettersafter the last try

What can trigger a function

SourceEventsResourceThe event carries
db.documentdocument.created, document.updated, document.deletedCollectiondata.id, data.collection, data.document (the new document; null when deleted)
db.rowrow.inserted, row.updated, row.deletedTabledata.table, data.operation, data.row_count — one event per statement; read the rows with SQL
auth.useruser.created, user.deleted—data.user_id, data.email, data.provider
storage.objectobject.created, object.deletedBucketdata.bucket, data.path, data.size, data.content_type
realtime.messagemessage.publishedChanneldata.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 event
import 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_key is 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 THROTTLED instead 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

FreeProUltraTeamEnterprise
Triggers per project2251002501,000
Tries per event38101215
Dead letters kept1 day7 days14 days30 days30 days

Each run counts as one invocation and its compute (GB-seconds), like a call. See Plan limits.

Something unclear or missing on this page?Tell us