Notify guides
14 · Scheduling, cancellation and routing

Later, only if needed, and not at night.

Scheduled sends you can cancel by your own key, follow-ups that go out only when the inbox item is still unread, and quiet hours.


1

Schedule and cancel

send_at (an ISO time) or delay_seconds, at most the type’s TTL ahead (422 SEND_AT_OUT_OF_RANGE otherwise). Preferences and devices are checked again at release time. A cancellation_key lets you cancel without having stored the notification id; it cancels everything still pending, scheduled or retrying under that key.

invite.ts
// Remind tomorrow at 08:00 UTC unless the user acts first.
await notify.notifications.send(
  {
    type: "invite.reminder",
    recipients: ["user_123"],
    data: { team: "Acme" },
    send_at: "2026-11-12T08:00:00Z", // or delay_seconds: 3600; at most the type's TTL ahead
    cancellation_key: "invite:inv_77",
  },
  { idempotencyKey: "invite-reminder:inv_77" },
);

// The user accepted the invite: cancel without having stored our id.
await notify.notifications.cancelByKey({ cancellation_key: "invite:inv_77" });

2

Inbox first, email if unread

The pattern most apps want: the inbox at once, push if the user has a device, and an email only if they still have not read the item 15 minutes later. Reading or archiving the item first ends the email step as read_in_inbox. A fallback step without after_unread_seconds runs only when the previous step had nothing to deliver or failed.

comment-created.ts
await notify.notificationTypes.replaceByKey("comment.created", {
  name: "New comment",
  category_key: "comments",
  ttl_seconds: 86400,
  routing: {
    always: ["inbox"],
    fallback: [
      { channel: "push" }, // straight away, when the user has a device
      { channel: "email", after_unread_seconds: 900 }, // only if the item is still unread 15 minutes later
    ],
  },
  throttle: { max: 5, per_seconds: 3600 }, // at most 5 an hour per recipient
  templates: [
    { channel: "inbox", locale: "en", body: { title: "{{author}} commented", body: "{{excerpt}}" } },
    { channel: "push", locale: "en", body: { title: "{{author}} commented", body: "{{excerpt}}" } },
    { channel: "email", locale: "en", body: { mail_template_name: "new-comment" } },
  ],
});

3

Quiet hours

A recipient’s own window wins, then the workspace default. A device’s own time zone wins for push. Critical types ignore quiet hours. Users set their own in <NotifyPreferences/>.

quiet-hours.ts
// Recipients without their own window get this one (an end before the
// start spans midnight). Push and web push wait; the inbox never does.
await notify.settings.update({
  default_timezone: "Europe/Lisbon",
  default_quiet_hours: { start: "22:00", end: "08:00" },
});
await notify.recipients.update("user_123", { timezone: "America/New_York", quiet_hours: { start: "23:00", end: "07:00" } });

4

Not in the preview yet

Batching and digests (“3 new comments”), topics you can subscribe recipients to, and tenant default preferences are planned. Until then a per-type throttle caps how often one person gets the same type.