Skip to main content

Notifications

A notification inbox record is durable state stored by agent.sendNotificationSignal(). It's not the same object as the notification signal delivered to a thread. The record tracks routing, priority, delivery state, deduplication, and retry history. The signal carries a summary and a snapshot of that record in its metadata.

Use Agent.sendNotificationSignal() to create records and the Notifications guide for the delivery flow.

Notification records
Direct link to Notification records

NotificationRecord is the stored inbox record.

id:

string
Unique notification record ID.

threadId:

string
Thread the notification targets.

source:

string
External system that produced the notification, such as github, slack, or email.

kind:

string
Notification kind within the source, such as ci-status, mention, or direct-message.

priority:

NotificationPriority
Delivery priority. Defaults to medium when the notification is created.

status:

NotificationStatus
Current lifecycle status of the record.

summary:

string
LLM-facing summary used as the notification signal contents.

payload?:

unknown
Structured payload stored for tools or application code. The model never receives this field as part of the signal.

resourceId?:

string
Resource that owns the thread, when known.

agentId?:

string
Agent that owns the notification, when known.

sourceId?:

string
Identifier of the source object, such as a pull request or message ID.

dedupeKey?:

string
Key used to coalesce duplicate pending notifications from the same source and thread.

coalesceKey?:

string
Key used to combine related pending notifications from the same source and thread.

coalescedCount?:

number
Number of notifications folded into this record.

attributes?:

NotificationSignalAttributes
Extra attributes copied onto the emitted notification signal as XML attributes.

createdAt:

Date
Time the record was created.

updatedAt:

Date
Time the record was last updated.

deliveredAt?:

Date
Time a full notification was delivered.

seenAt?:

Date
Time the notification was marked seen, including when the inbox tool read it.

dismissedAt?:

Date
Time the notification was dismissed.

archivedAt?:

Date
Time the notification was archived.

discardedAt?:

Date
Time the notification was discarded by the delivery policy.

deliverAt?:

Date
Earliest time the dispatcher may attempt full delivery.

summaryAt?:

Date
Time the dispatcher should emit a summary for this record. Cleared after the summary is emitted.

deliveryReason?:

string
Reason returned by the delivery policy for the current decision.

deliveryAttempts?:

number
Number of failed delivery attempts. Reaching five marks the record failed.

lastDeliveryAttemptAt?:

Date
Time of the most recent delivery attempt.

lastDeliveryError?:

string
Error from the most recent failed delivery attempt.

deliveredSignalId?:

string
Signal ID of the full notification delivery, when one was emitted.

summarySignalId?:

string
Signal ID of the most recent summary that included this record.

metadata?:

Record<string, unknown>
Application metadata stored on the record.

Coalescing pending records
Direct link to Coalescing pending records

sendNotificationSignal() can merge a new notification into an existing pending record instead of creating another one. Merging only considers records on the same thread, source, kind, agent, and resource.

Each key serves a different purpose:

  • dedupeKey identifies the same event. Reuse it when a retry or duplicate webhook should update the existing record.
  • coalesceKey groups related events. Use it when several events should become one record, such as multiple failures from the same pipeline.

Both keys use the same matching rules. The difference is intent: a dedupe key represents one event that may arrive twice, while a coalesce key represents a set of related events.

When a notification merges:

  • The latest summary and payload replace the previous values.
  • priority updates when the new notification provides one.
  • attributes and metadata merge, with the new values taking precedence.
  • coalescedCount increments.

Priorities and statuses
Direct link to Priorities and statuses

NotificationPriority controls default delivery timing.

type NotificationPriority = 'low' | 'medium' | 'high' | 'urgent'

NotificationStatus is the record lifecycle.

type NotificationStatus =
| 'pending'
| 'delivered'
| 'seen'
| 'dismissed'
| 'archived'
| 'discarded'
| 'failed'

pending records are eligible for dispatch. delivered means a full notification signal was emitted. seen, dismissed, and archived are inbox states. discarded means the delivery policy rejected the notification. failed means delivery reached the five-attempt cap.

Delivery decisions
Direct link to Delivery decisions

A delivery policy returns a NotificationDeliveryDecision.

type NotificationDeliveryAction =
| 'deliver'
| 'queue'
| 'defer'
| 'summarize'
| 'persist'
| 'discard'

type NotificationDeliveryDecision = {
action: NotificationDeliveryAction
deliverAt?: Date
summaryAt?: Date
reason?: string
streamOptions?: AgentExecutionOptions<unknown>
hold?: boolean
}

deliver emits a full notification signal immediately. queue and defer leave the record pending for later dispatch. summarize emits or schedules a summary. persist stores the record without emitting a signal. discard marks the record discarded without delivering it.

streamOptions are attached when delivery wakes an idle thread. They're resolved at delivery time, so a deferred or summarized notification can still carry the execution options the woken thread needs, such as model selection.

hold applies only at delivery time. It leaves the notification pending without counting an attempt, so another process's dispatcher can deliver it. Use it when a process shares the notification store but can't run the target thread. hold is ignored when the notification is first sent.

Delivery policies
Direct link to Delivery policies

Configure a policy through the Agent constructor's notifications.deliveryPolicy option.

type NotificationDeliveryPolicyConfig = {
default?: NotificationDeliveryAction | NotificationDeliveryDecision
priorities?: Partial<
Record<NotificationPriority, NotificationDeliveryAction | NotificationDeliveryDecision>
>
sources?: Record<string, NotificationDeliveryAction | NotificationDeliveryDecision>
decide?: (
input: NotificationDeliveryPolicyInput,
) =>
| NotificationDeliveryAction
| NotificationDeliveryDecision
| undefined
| Promise<NotificationDeliveryAction | NotificationDeliveryDecision | undefined>
}

type NotificationDeliveryPolicyInput = {
record: NotificationRecord
threadState: 'active' | 'idle'
now: Date
}

Mastra evaluates the policy in this order:

  1. decide
  2. sources, matched against record.source
  3. priorities, matched against record.priority
  4. default
  5. The built-in priority policy

The built-in policy is:

PriorityIdle threadActive thread
urgentDeliver immediatelyDeliver immediately
highDeliver immediatelyEmit a summary immediately, then deliver later when the thread is idle
mediumDeliver immediatelyBatch into a summary
lowBatch into a summaryBatch into a summary

A custom decide function runs at receipt time and again at delivery time for records whose receipt-time decision deferred them or scheduled a summary. At delivery time, the record's persisted schedule already fixes when and how it delivers, so only streamOptions and hold are honored. Keep the decider side-effect free.

Dispatch
Direct link to Dispatch

NotificationDispatchConfig controls the scheduled dispatcher.

enabled?:

boolean
Whether automatic scheduled dispatch is enabled. Defaults to true; set false to opt out.

cron?:

string
Cron expression for the dispatcher schedule. Defaults to */1 * * * * (once per minute).

batchSize?:

number
Maximum notifications processed per dispatch. Defaults to 100.

The dispatcher schedule is created lazily on the first deferred or summarized notification, so an app with no deferred notifications never starts it. A record that fails delivery five times is marked failed and stops being due. The cap is exported as MAX_NOTIFICATION_DELIVERY_ATTEMPTS.

Delivered signal metadata
Direct link to Delivered signal metadata

When a record delivers, the stream consumer receives a notification signal with NotificationSignalMetadata.

type NotificationSignalMetadata = {
signal: 'notification'
recordId: string
source: string
kind: string
priority: NotificationPriority
status: NotificationStatus
coalescedCount?: number
deliveredAt?: string
seenAt?: string
}

When Mastra emits a summary, the consumer receives a summary signal with NotificationSummarySignalMetadata.

type NotificationSummarySignalMetadata = {
signal: 'summary'
pending: number
groups: Array<{ source: string; count: number }>
byPriority: Partial<Record<NotificationPriority, number>>
notificationIds: string[]
priority?: NotificationPriority
}

These metadata shapes describe the signals. The stored record isn't the signal, and the signal doesn't include the record's payload or retry history.

Storage
Direct link to Storage

Notification records require a storage domain that supports notifications, such as libSQL, PostgreSQL, or MongoDB. Without it, sendNotificationSignal() can't store records.

sendSignal({ type: 'notification' }) bypasses inbox storage and delivers notification-shaped context directly. Use createNotificationInboxTool() to read and manage stored records from an agent.