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 recordsDirect link to Notification records
NotificationRecord is the stored inbox record.
id:
threadId:
source:
github, slack, or email.kind:
ci-status, mention, or direct-message.priority:
medium when the notification is created.status:
summary:
payload?:
resourceId?:
agentId?:
sourceId?:
dedupeKey?:
coalesceKey?:
coalescedCount?:
attributes?:
createdAt:
updatedAt:
deliveredAt?:
seenAt?:
dismissedAt?:
archivedAt?:
discardedAt?:
deliverAt?:
summaryAt?:
deliveryReason?:
deliveryAttempts?:
failed.lastDeliveryAttemptAt?:
lastDeliveryError?:
deliveredSignalId?:
summarySignalId?:
metadata?:
Coalescing pending recordsDirect 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:
dedupeKeyidentifies the same event. Reuse it when a retry or duplicate webhook should update the existing record.coalesceKeygroups 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
summaryandpayloadreplace the previous values. priorityupdates when the new notification provides one.attributesandmetadatamerge, with the new values taking precedence.coalescedCountincrements.
Priorities and statusesDirect 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 decisionsDirect 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 policiesDirect 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:
decidesources, matched againstrecord.sourcepriorities, matched againstrecord.prioritydefault- The built-in priority policy
The built-in policy is:
| Priority | Idle thread | Active thread |
|---|---|---|
urgent | Deliver immediately | Deliver immediately |
high | Deliver immediately | Emit a summary immediately, then deliver later when the thread is idle |
medium | Deliver immediately | Batch into a summary |
low | Batch into a summary | Batch 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.
DispatchDirect link to Dispatch
NotificationDispatchConfig controls the scheduled dispatcher.
enabled?:
true; set false to opt out.cron?:
*/1 * * * * (once per minute).batchSize?:
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 metadataDirect 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.
StorageDirect 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.