Skip to main content

Messages

Looking for the product view?
The Help Center explains this surface as users see it: Messages.

Apps can send push notifications and emails to users through a durable message queue. Messages are enqueued via the notify() and email() callbacks in server route handlers, webhook handlers, and agent tools, then delivered asynchronously by a background dispatcher with retry and dead-letter handling.

Message Lifecycle​

pending → dispatched → delivered (success)
pending → dispatched → pending (failure, will retry)
pending → dispatched → dead (max retries exceeded)
dead/failed → pending (manual retry via API)

Channels​

ChannelRecipientDelivery Method
pushInformer usernameFCM to all registered devices (Informer GO)
emailEmail addressTenant mail transport (SMTP, Gmail API, Microsoft Graph)

Push notifications include the app's ID automatically — tapping a notification in Informer GO opens the app, optionally at a specific sub-page via the path field.


GET /api/apps/{id}/messages​

List messages for an app with optional filters and cursor-based pagination.

Authentication: Required

Permissions Required: Member+ role (write permission)

Path Parameters:

ParameterTypeDescription
idstringApp UUID or natural ID

Query Parameters:

ParameterTypeDefaultDescription
channelstring—Filter by channel: push or email
statusstring—Filter by status: pending, dispatched, delivered, failed, dead
recipientstring—Filter by recipient (exact match)
limitnumber100Max results (1–500)
beforeISO date—Cursor: return messages created before this timestamp
List an App's messagesGET /api/apps/admin:order-desk/messages
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/messages"
}
},
"messages": [
{
"id": "00000000-0000-4000-8000-000000000001",
"channel": "push",
"recipient": "annie.larson",
"payload": {
"body": "Order #1042 left the warehouse",
"path": "/orders/1042",
"title": "Order shipped"
},
"status": "delivered",
"attempts": 1,
"maxAttempts": 3,
"error": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"processedAt": "2026-01-15T16:20:00.000Z"
},
{
"id": "00000000-0000-4000-8000-000000000002",
"channel": "push",
"recipient": "abel.jacobs",
"payload": {
"body": "Chai is below reorder point",
"title": "Low stock"
},
"status": "pending",
"attempts": 0,
"maxAttempts": 3,
"error": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"processedAt": null
},
{
"id": "00000000-0000-4000-8000-000000000003",
"channel": "email",
"recipient": "carol@example.com",
"payload": {
"to": "carol@example.com",
"html": "<p>12 orders today</p>",
"subject": "Daily order summary"
},
"status": "delivered",
"attempts": 1,
"maxAttempts": 3,
"error": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"processedAt": "2026-01-15T16:20:00.000Z"
},
"… 1 more items (4 total) — omitted from docs"
],
"summary": [
{
"status": "delivered",
"count": 2
},
{
"status": "failed",
"count": 1
},
{
"status": "pending",
"count": 1
}
],
"daily": [
{
"day": "2026-01-15",
"channel": "email",
"count": 2
},
{
"day": "2026-01-15",
"channel": "push",
"count": 2
}
]
}
Captured from the API examples test suite; ids and timestamps are normalized.

The summary provides status counts (useful for dashboard indicators) and daily provides per-day, per-channel volume.

Filter by channelGET /api/apps/admin:order-desk/messages?channel=email
channel and status filters combine with recipient, limit, and the before cursor.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/messages"
}
},
"messages": [
{
"id": "00000000-0000-4000-8000-000000000001",
"channel": "email",
"recipient": "carol@example.com",
"payload": {
"to": "carol@example.com",
"html": "<p>12 orders today</p>",
"subject": "Daily order summary"
},
"status": "delivered",
"attempts": 1,
"maxAttempts": 3,
"error": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"processedAt": "2026-01-15T16:20:00.000Z"
},
{
"id": "00000000-0000-4000-8000-000000000002",
"channel": "email",
"recipient": "dave@example.com",
"payload": {
"to": "dave@example.com",
"html": "<p>1 failure</p>",
"subject": "Webhook failure digest"
},
"status": "failed",
"attempts": 2,
"maxAttempts": 3,
"error": "Connection refused",
"createdAt": "2026-01-15T16:20:00.000Z",
"processedAt": null
}
],
"summary": [
{
"status": "delivered",
"count": 2
},
{
"status": "failed",
"count": 1
},
{
"status": "pending",
"count": 1
}
],
"daily": [
{
"day": "2026-01-15",
"channel": "email",
"count": 2
},
{
"day": "2026-01-15",
"channel": "push",
"count": 2
}
]
}
Captured from the API examples test suite; ids and timestamps are normalized.

Cursor Pagination:

Use the createdAt of the last message as the before parameter:

// First page
const page1 = await fetch('/api/apps/{id}/messages?limit=50');

// Next page
const lastDate = page1.messages[page1.messages.length - 1].createdAt;
const page2 = await fetch(`/api/apps/{id}/messages?limit=50&before=${lastDate}`);

GET /api/apps/{id}/messages/{messageId}​

Get a single message by ID.

Authentication: Required

Permissions Required: Member+ role (write permission)

Path Parameters:

ParameterTypeDescription
idstringApp UUID or natural ID
messageIdstringMessage UUID

Response: Single message object (same shape as items in the list response).

Get a single messageGET /api/apps/admin:order-desk/messages/00000000-0000-4000-8000-000000000001
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/messages/00000000-0000-4000-8000-000000000001"
}
},
"id": "00000000-0000-4000-8000-000000000001",
"tenant": "acme",
"appId": "00000000-0000-4000-8000-000000000002",
"channel": "push",
"recipient": "annie.larson",
"payload": {
"body": "Order #1042 left the warehouse",
"path": "/orders/1042",
"title": "Order shipped"
},
"status": "delivered",
"attempts": 1,
"maxAttempts": 3,
"error": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"processedAt": "2026-01-15T16:20:00.000Z"
}
Captured from the API examples test suite; ids and timestamps are normalized.

Error Responses:

  • 404 Not Found — Message does not exist or does not belong to this app

POST /api/apps/{id}/messages/{messageId}/_retry​

Retry a failed or dead message. Resets the message to pending status with zero attempts so the dispatcher will pick it up again.

Authentication: Required

Permissions Required: Member+ role (write permission)

Path Parameters:

ParameterTypeDescription
idstringApp UUID or natural ID
messageIdstringMessage UUID

Response: The updated message object with status: "pending".

Retry a failed messagePOST /api/apps/admin:order-desk/messages/00000000-0000-4000-8000-000000000001/_retry
Retry re-queues failed or dead messages; the dispatcher picks them up on its next pass.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/apps/admin%3Aorder-desk/messages/00000000-0000-4000-8000-000000000001/_retry"
}
},
"id": "00000000-0000-4000-8000-000000000001",
"tenant": "acme",
"appId": "00000000-0000-4000-8000-000000000002",
"channel": "email",
"recipient": "dave@example.com",
"payload": {
"to": "dave@example.com",
"html": "<p>1 failure</p>",
"subject": "Webhook failure digest"
},
"status": "pending",
"attempts": 0,
"maxAttempts": 3,
"error": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"processedAt": null
}
Captured from the API examples test suite; ids and timestamps are normalized.

Error Responses:

  • 404 Not Found — Message does not exist
  • 409 Conflict — Message is not in a retryable state (e.g., already pending or delivered)

Sending Messages from App Code​

Messages are sent from server route handlers, webhook handlers, and agent tools using the notify() and email() callbacks. See the handler documentation for full API details.

Quick Reference​

// Push notification
const { id } = await notify('username', {
title: 'Alert',
body: 'Something happened',
path: '/details/123' // optional deep link
});

// Email
const { id } = await email('user@example.com', {
subject: 'Report Ready',
html: '<p>Your report is attached.</p>',
from: 'noreply@example.com' // optional
});

// Bulk push
const { ids, queued } = await notify([
{ username: 'alice', title: 'Update', body: '...' },
{ username: 'bob', title: 'Update', body: '...' },
]);

// Bulk email
const { ids, queued } = await email([
{ to: 'alice@example.com', subject: 'Update', html: '...' },
{ to: 'bob@example.com', subject: 'Update', html: '...' },
]);

HAL Relations​

RelationDescription
inf:app-messagesLink to the app's message list
inf:app-messageLink to a single message
inf:app-message-retryRetry a failed/dead message