> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ravenna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Reminders

> Automatically nudge pending approvers and ticket assignees on a configurable schedule until they act, the work completes, or the reminder cap is reached.

Reminders automatically nudge people who have not acted on work assigned to them. Ravenna handles the follow-ups, posting a public message on the ticket and @mentioning whoever still needs to respond. There are two types:

* **Approval reminders** nudge approvers who have not yet responded to a pending [approval round](/documentation/tickets/approvals/rounds).
* **Assignment reminders** nudge the current assignee of a ticket that has been sitting in an open state.

Both are configured per workspace in **Settings** → **Workspace** → **Automation**. Each workspace owns its own policies, so IT and HR can run on different cadences.

<CardGroup cols={2}>
  <Card title="Approval reminders" icon="check" href="/documentation/tickets/reminders#approval-reminders">
    Nudge approvers who have not yet responded to a pending approval round.
  </Card>

  <Card title="Assignment reminders" icon="user-check" href="/documentation/tickets/reminders#assignment-reminders">
    Nudge the current assignee of a ticket that has been sitting in an open state.
  </Card>
</CardGroup>

<View title="Human" icon="user">
  ***

  ## Shared behavior

  Approval and assignment reminders work the same way under the hood:

  * Each is a workspace-level policy with an **Enable** toggle, a **reminder interval** (hours, days, or weeks, default `24 hours`), a **maximum reminders** count (`1`, `2`, `3`, `5`, `10`, or **Unlimited**, default `3`), an optional **business schedule**, and optional **conditions** that scope which tickets the policy applies to.
  * Each reminder is a public, system-authored message posted to the ticket from the Ravenna bot that @mentions whoever still needs to act. It is visible to everyone with access to the ticket and is delivered through the same channels as any other public ticket message (Slack thread, the Admin, email mirror).
  * The interval clock restarts after every reminder. It is measured from when the work started (or from the last reminder), not from ticket creation.
  * When the maximum is reached, the work itself is unaffected. Ravenna fires a [Reminder Expired](/documentation/automate/workflows/triggers-actions#reminder-expired) workflow trigger so you can escalate, reassign, or close the ticket in a workflow.

  ### Conditions

  You can scope a policy to a subset of tickets by adding **conditions**. Each policy can have one or more condition groups built from the same ticket fields you use elsewhere in Ravenna (queue, priority, form, requester attributes, and so on).

  * Leave conditions empty to remind on every ticket the policy type applies to. This is the default.
  * Add one or more conditions and the policy only sends reminders for tickets that match. Within a group, all conditions must match (AND). Across groups, any group matching is enough (OR).
  * Conditions are re-evaluated **every time a reminder is about to fire**, not just when the work starts. A ticket that no longer matches is skipped for that cycle, but the schedule keeps running. If the ticket changes back into scope (for example, priority is raised back to `High`), reminders resume on the next interval.
  * Skipped fires do not count toward the **Maximum reminders** cap. Only delivered reminders count.
  * To prevent a never-matching ticket from rescheduling forever, a reminder chain is capped at **30 days** from when it first scheduled. After that, the chain stops on its own without firing a Reminder Expired event.

  ### Business hours and weekends

  Reminders respect the **business schedule** you select on the policy. If a reminder would fire outside the schedule's working windows, Ravenna defers it to the next working window so people are not pinged overnight or on weekends.

  * If the policy has no schedule selected, reminders fire on a straight wall-clock interval, including nights and weekends.
  * The interval is measured in real time, not business time. A 24-hour interval still resumes 24 hours after the work started; the deferral only shifts the *delivery* moment forward to the next working window.
  * Only schedules that belong to the same workspace can be selected.

  <Info>
    Automation settings are admin-only. Each workspace has its own policies. There is no per-queue, per-form, or per-ticket override. Disable a workspace's policy to stop those reminders everywhere in that workspace.
  </Info>

  ***

  ## Approval reminders

  When a round becomes active, Ravenna schedules the first reminder using your workspace's interval setting. Each time the reminder fires:

  1. Ravenna looks up the round's pending approvers (anyone who has not yet approved or declined).

  2. If there are no pending approvers, nothing is sent and no further reminders are scheduled. The round has either completed or been reset.

  3. Otherwise, Ravenna posts a public message that @mentions every pending approver:

     > `@Alice, @Bob, this is a reminder that your approval is pending on this ticket.`

  4. If the round still has pending approvers and the cap has not been reached, Ravenna schedules the next reminder one interval out from the message it just sent.

  5. When the cap is reached, Ravenna fires a [Reminder Expired](/documentation/automate/workflows/triggers-actions#reminder-expired) workflow trigger of type `Approval`.

  Reminders are tied to the **round**, not the ticket. A ticket with three sequential rounds gets up to three independent reminder cycles, one per round, each starting when the round activates.

  <Info>
    Approvers who are added to a round mid-flight (for example, when an admin edits the round) are picked up automatically on the next reminder. They do not need to wait for a new cycle to start.
  </Info>

  Approval reminders do **not** send a fresh Slack DM. They reuse the standard ticket message surface so the reminder shows up in the existing approval conversation, not a separate inbox. Approvers respond using the existing Approve and Decline controls in the ticket or the original approval Slack DM. To re-prompt via DM, reset the round.

  ### Configure approval reminders

  <Steps>
    <Step title="Open Automation settings">
      Go to **Settings** → **Workspace** → **Automation**. The page is admin-only.
    </Step>

    <Step title="Enable approval reminders">
      Find the **Approval Reminders** card and toggle **Enable approval reminders** on.
    </Step>

    <Step title="Set the reminder interval">
      Set how long Ravenna waits after a round opens (or after the last reminder) before sending the next nudge. Enter a whole number and select **hours**, **days**, or **weeks**. The default is `24 hours`.
    </Step>

    <Step title="Set the maximum reminders">
      Set how many nudges Ravenna sends per round before giving up. Select from `1`, `2`, `3`, `5`, `10`, or **Unlimited**. The default is `3`.
    </Step>

    <Step title="Select a business schedule (optional)">
      Under **Business hours**, select a [business schedule](/documentation/platform/workspaces/settings#business-schedules) to restrict when reminders can fire. Leave it set to **No schedule** to send on a wall-clock interval, including nights and weekends.
    </Step>

    <Step title="Add conditions (optional)">
      Under **Conditions**, select **Add condition set** to limit reminders to tickets matching specific filters (for example, only `Priority` is `High` or `Urgent`). Leave empty to remind on every pending approval round.
    </Step>
  </Steps>

  ### When approval reminders stop

  A reminder cycle ends as soon as any of the following happens:

  * All approvers on the round have responded (the round is approved, declined, or reset).
  * The round has no pending approvers when a reminder is about to fire.
  * The configured **Maximum reminders** count has been sent.
  * An admin disables the **Enable approval reminders** toggle.
  * The reminder chain has been running for 30 days without delivering, because the ticket never matched the policy's conditions.

  ***

  ## Assignment reminders

  When a ticket is assigned, Ravenna schedules the first reminder using your workspace's interval setting. Each time the reminder fires:

  1. Ravenna checks the ticket. If the ticket is unassigned, was reassigned to someone else, archived, or has moved to a terminal status (`Done` or `Closed`), the reminder is cancelled.

  2. Otherwise, Ravenna posts a public message that @mentions the current assignee:

     > `@Alice, this is a reminder to follow up on a ticket assigned to you.`

  3. If the cap has not been reached, Ravenna schedules the next reminder one interval out from the message it just sent.

  4. When the cap is reached, Ravenna fires a [Reminder Expired](/documentation/automate/workflows/triggers-actions#reminder-expired) workflow trigger of type `Assignment`.

  Reminders are tied to the **ticket and its current assignee**. Reassigning the ticket replaces the schedule with a fresh cycle for the new assignee.

  <Info>
    Only one assignment reminder runs at a time per ticket. The schedule is keyed by ticket, not by assignee, so a new assignment supersedes the previous reminder.
  </Info>

  ### Configure assignment reminders

  <Steps>
    <Step title="Open Automation settings">
      Go to **Settings** → **Workspace** → **Automation**. The page is admin-only.
    </Step>

    <Step title="Enable assignment reminders">
      Find the **Assignment Reminders** card and toggle **Enable assignment reminders** on.
    </Step>

    <Step title="Set the reminder interval">
      Set how long Ravenna waits after a ticket is assigned (or after the last reminder) before sending the next nudge. Enter a whole number and select **hours**, **days**, or **weeks**. The default is `24 hours`.
    </Step>

    <Step title="Set the maximum reminders">
      Set how many nudges Ravenna sends per assigned ticket before giving up. Select from `1`, `2`, `3`, `5`, `10`, or **Unlimited**. The default is `3`.
    </Step>

    <Step title="Select a business schedule (optional)">
      Under **Business hours**, select a [business schedule](/documentation/platform/workspaces/settings#business-schedules) to restrict when reminders can fire. Leave it set to **No schedule** to send on a wall-clock interval, including nights and weekends.
    </Step>

    <Step title="Add conditions (optional)">
      Under **Conditions**, select **Add condition set** to limit reminders to tickets matching specific filters (for example, only tickets in a particular queue, or only `Priority` is `High` or `Urgent`). Leave empty to remind on every assigned ticket.
    </Step>
  </Steps>

  ### When assignment reminders stop

  A reminder cycle ends as soon as any of the following happens:

  * The ticket is **unassigned** or reassigned to a different user. A new cycle begins for the new assignee.
  * The ticket is **archived**.
  * The ticket moves to a **terminal status** (any status in the `Done` or `Closed` system group).
  * The configured **Maximum reminders** count has been sent.
  * An admin disables the **Enable assignment reminders** toggle.
  * The reminder chain has been running for 30 days without delivering, because the ticket never matched the policy's conditions.

  When the maximum is reached, the ticket stays open and assigned. The signal that reminders have given up is the **Reminder Expired** workflow trigger.

  ***

  ## Escalate stale work with workflows

  Pair reminders with the [Reminder Expired](/documentation/automate/workflows/triggers-actions#reminder-expired) workflow trigger to escalate work that exhausts its reminder budget without progress. Filter the trigger's **Reminder Type** to `Approval` or `Assignment` so the workflow only fires for the reminder type you intend.

  Common patterns:

  * **Notify a backup approver or the assignee's manager.** Add a manager or fallback group when the cap is hit so the request keeps moving.
  * **Reassign to a fallback group.** Move the ticket to a backup queue or assign it to a different team so it does not stall.
  * **Post an escalation note.** Add a [private note](/documentation/tickets/private-notes) summarizing who is still pending and ping a lead for triage.
  * **Auto-close stalled requests.** If a request has been ignored for too long, change the ticket status or decline the round in a workflow.

  <Callout icon="link" color="#6B7280">
    Learn more about the [Reminder Expired trigger](/documentation/automate/workflows/triggers-actions#reminder-expired) and [building escalation workflows](/documentation/automate/workflows/overview).
  </Callout>

  ***

  ## Tips

  * **Start conservative.** A 24-hour interval with a 3-reminder cap is the default for a reason. Tighter cadences create noise without changing outcomes.
  * **Select a business schedule.** Without one, reminders fire 24/7 and people get pinged in the middle of the night.
  * **Use conditions to focus the policy.** If only high-priority or queue-specific work warrants nudging, add conditions so routine tickets are not pinged. Conditions are re-checked on every fire, so a ticket can drop out and back into scope without losing its place.
  * **Use Waiting statuses to pause assignment reminders.** Assignment reminders only stop when a ticket reaches a terminal status group (`Done` or `Closed`). Use [statuses](/documentation/tickets/organize/statuses) and [SLA pauses](/documentation/automate/slas) deliberately so reminders fire only when the assignee actually owns the next step.
  * **Always design the expiry path.** Reminders only nudge, they do not move the ticket. Build a Reminder Expired workflow so stalled work gets reassigned or escalated automatically.
  * **Combine approval reminders with [Wait for Approval](/documentation/automate/workflows/triggers-actions#wait-for-approval) timeouts.** Reminders push approvers to act; the workflow's On Timeout branch handles the case where they never do.
</View>

<View title="Agent" icon="bot">
  ## Mental model

  Approval and assignment reminders are workspace-level automation policies that schedule recurring nudge messages on tickets. Both use the shared `Reminder` infrastructure, the same scheduling primitive keyed by a URN, and both dispatch a `REMINDER_EXPIRED` ticket event when the cap is hit.

  Each policy is configured once per workspace. There is no per-ticket, per-queue, per-form, per-round, or per-assignee override.

  * **Approval reminders** are keyed by `(ticketId, roundId)`. Scheduled when a round opens, cancelled when the round closes (approve, decline, reset).
  * **Assignment reminders** are keyed by `ticketId`. Scheduled when a ticket is assigned, cancelled when the ticket is unassigned, reassigned, archived, or moves to `Done` or `Closed`.

  ***

  ## Configuration

  Each workspace has at most one policy of each type. Both expose the same fields:

  | Field           | Type                         | Default | Notes                                                                                                                                                                                                  |
  | --------------- | ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
  | `enabled`       | boolean                      | `false` | Master switch. `false` means no reminders are scheduled or dispatched even if other fields are set                                                                                                     |
  | `intervalHours` | number ≥ 1                   | `24`    | Hours between the work starting (or the last reminder) and the next reminder                                                                                                                           |
  | `maxReminders`  | integer ≥ 1 or `null`        | `3`     | Maximum reminders per round (approval) or per assigned ticket (assignment). `null` means unlimited                                                                                                     |
  | `scheduleId`    | string or `null`             | `null`  | Optional [business schedule](/documentation/platform/workspaces/settings#business-schedules) ID used to defer delivery to working windows. Must belong to the same workspace                           |
  | `filterGroups`  | filter-group array or `null` | `null`  | Optional ticket condition filter, expressed as an OR of AND groups. Empty/null means "remind for every ticket". Evaluated against the ticket's hydrated context every time a reminder is about to fire |

  The UI exposes the interval in hours, days, or weeks, but the value is normalized to hours on save. Days and weeks are converted via `24` and `168` respectively.

  `computeReminderFireAt(baseTime, intervalHours, schedule)` returns `baseTime + intervalHours` adjusted forward to the next working window of the policy's selected business schedule. If the policy has no schedule, the returned time is the raw wall-clock offset. The schedule is the one attached to the policy, not the workspace default. Upserting a policy with a `scheduleId` that does not belong to the same workspace is rejected.

  ***

  ## Approval reminder lifecycle

  1. **Round opens.** When an approval round transitions to ACTIVE, an approval reminder is scheduled for the round.
  2. **Policy check.** The workspace's approval reminder policy is loaded. If `enabled = false`, no reminder is scheduled.
  3. **First reminder scheduled.** A reminder is enqueued with:
     * `uri`: `REMINDER_URN.approvalRound(ticketId, roundId)`, which guarantees idempotency per round.
     * `type`: `ReminderType.Approval`.
     * `fireAt`: `computeReminderFireAt(round.openedAt, intervalHours, policy.schedule)`.
     * `metadata`: `{ ticketId, roundId, workspaceId, organizationId, reminderCount: 0 }`.
  4. **Reminder fires.** The handler runs:
     * Re-fetches pending approvers. Empty list → cancels the reminder URN and returns (round resolved between fire-at and execution).
     * Re-reads the policy. If `enabled = false` mid-cycle → cancels and returns.
     * Evaluates `policy.filterGroups` against the hydrated ticket context. On no match, the reminder is marked `skipped` and **no message is posted**; on match, posts the system message below.
     * Posts a system message authored by the workspace bot user with `@` mentions for each pending approver. Source is `SYSTEM`. Message metadata records `reminderType`, `reminderCount`, `intervalHours`, `maxReminders`, and `recipientIds`.
     * Computes `nextCount`. A delivered fire increments it; a `skipped` fire leaves it unchanged. If `maxReminders !== null && nextCount >= maxReminders`, dispatches a `REMINDER_EXPIRED` ticket event with `reminderType: Approval`. Otherwise, if the chain has been running less than `REMINDER_CHAIN_MAX_AGE_DAYS` (30 days from `chainStartedAt`), schedules the next reminder. Past that ceiling the chain stops without dispatching `REMINDER_EXPIRED`.
  5. **Round closes.** When the round becomes APPROVED, DECLINED, or RESET, the reminder is cancelled via the URN.

  URN:

  ```text theme={"system"}
  ravenna:reminder:approval-round:{ticketId}:{roundId}
  ```

  Scheduling with the same URN replaces any existing reminder for that key, so re-opening or re-activating a round does not double-schedule.

  ***

  ## Assignment reminder lifecycle

  1. **Assignee set.** When a ticket gains an assignee, an assignment reminder is scheduled.
  2. **Policy check.** The workspace's assignment reminder policy is loaded. If `enabled = false`, no reminder is scheduled.
  3. **First reminder scheduled.** A reminder is enqueued with:
     * `uri`: `REMINDER_URN.assignment(ticketId)`, one in-flight reminder per ticket.
     * `type`: `ReminderType.Assignment`.
     * `fireAt`: `computeReminderFireAt(now, intervalHours, policy.schedule)`.
     * `metadata`: `{ ticketId, assigneeId, organizationId, workspaceId, reminderCount: 0 }`.
  4. **Reminder fires.** The handler runs:
     * Reloads the ticket. If the ticket is missing or no longer assigned to `metadata.assigneeId`, is archived, or its status group is in `TERMINAL_STATUS_GROUPS` (`Done`, `Closed`) → cancels the URN and returns.
     * Re-reads the policy. If `enabled = false` mid-cycle → cancels and returns.
     * Evaluates `policy.filterGroups` against the hydrated ticket context. On no match, the reminder is marked `skipped` and **no message is posted**; on match, posts the system message below.
     * Posts a system message authored by the workspace bot user with an `@` mention for the assignee. Source is `SYSTEM`. Message metadata records `reminderType`, `reminderCount`, `intervalHours`, `maxReminders`, and `recipientIds`.
     * Computes `nextCount`. A delivered fire increments it; a `skipped` fire leaves it unchanged. If `maxReminders !== null && nextCount >= maxReminders`, dispatches a `REMINDER_EXPIRED` ticket event with `reminderType: Assignment`. Otherwise, if the chain has been running less than `REMINDER_CHAIN_MAX_AGE_DAYS` (30 days from `chainStartedAt`), schedules the next reminder. Past that ceiling the chain stops without dispatching `REMINDER_EXPIRED`.
  5. **Assignment changes or ticket closes.** The URN is cancelled. Reassigning to a new user schedules a fresh cycle keyed on the same URN, which supersedes any prior schedule.

  URN:

  ```text theme={"system"}
  ravenna:reminder:assignment:{ticketId}
  ```

  One reminder per ticket. Reassignment replaces the schedule rather than running two cycles in parallel.

  ***

  ## REMINDER\_EXPIRED dispatch

  When `maxReminders` is hit, the handler dispatches a `TicketEventAction.REMINDER_EXPIRED` event with:

  * `ticketId`: the ticket
  * `reminderType`: `Approval` or `Assignment`
  * `organizationId`
  * `source`: `SYSTEM`

  The **Reminder Expired** workflow trigger consumes this event. Filter by `Reminder Type` to target a specific reminder type. The round or ticket is **not** modified by the expiry. Any state change must come from a workflow or manual admin action.

  ***

  ## Constraints and gotchas

  * Each workspace has at most one policy of each type. There is no per-queue, per-form, or per-ticket override.
  * Approval reminders are per-round. A ticket with three sequential rounds gets three independent reminder cycles. Assignment reminders are per-ticket-per-assignee. Reassigning a ticket cancels the existing cycle (via the URN) and starts a new one with `reminderCount: 0`.
  * The `intervalHours` clock restarts after every reminder. It is measured from "work started or last reminder", not from ticket creation.
  * The cap (`maxReminders`) counts dispatched reminders, not scheduled ones. If a reminder fires and is cancelled early (no pending approvers, terminal status, mismatched assignee, archived), or is `skipped` because the ticket did not match `filterGroups`, the count is not incremented.
  * `filterGroups` is evaluated against the ticket's hydrated context (reference id `reminder`, section `input`) on every fire. A non-matching fire is recorded with status `skipped` instead of `fired`, no message is posted, and the chain reschedules without advancing the count. Reminder records can be in one of four statuses: `pending`, `fired`, `skipped`, or `cancelled`.
  * Reminder chains carry `chainStartedAt`, the wall-clock time the first reminder was scheduled. Once the chain age exceeds `REMINDER_CHAIN_MAX_AGE_DAYS` (30 days), the handler stops rescheduling and does **not** fire `REMINDER_EXPIRED`. This prevents a ticket that never matches its policy filters from rescheduling forever. Chains scheduled before this field existed have no cap.
  * Reminder messages are public ticket messages, visible to everyone with access to the ticket including the requester via Slack mirrors and email.
  * Tickets in any sub-status of the `Done` or `Closed` status groups stop assignment reminders. Tickets in `Waiting` do **not**, so move a ticket to `Done` if you want reminders to cease.
  * Approval reminders do not send a fresh Slack DM. They rely on the existing approval DM plus the new public `@` mention. To re-prompt via DM, reset the round.
  * Disabling a policy mid-cycle cancels future dispatches but does not delete already-scheduled reminders. The handler re-checks the policy at fire time and cancels the URN if disabled.
</View>
