Skip to content
Esc
↑↓navigate↵open⌘Jpreview
On this page

Messages

Send the posts, comments, and replies inside a source container

A message is one post, comment, or reply inside a source container: a chat message in a channel, a customer’s reply on a ticket, a comment on an issue. Messages are what Modem reads; source containers tell it how to read them.

Three event types manage messages: message.created, message.updated, and message.deleted. All use the standard envelope.

The source container reference

Every message carries a reference to the source container it belongs to:

"source_container": {
    "kind": "support_thread",
    "external_id": "48213"
}

Modem resolves the reference using the message’s source_name plus these two fields, so all three must match the source container’s identity exactly.

The reference is what connects a message to processing. Messages are queued against their source container and drained in order, with the source container’s kind, title, description, and lifecycle state as context. Without a matching source container:

  • A message with a reference to a source container Modem has not seen yet is stored and waits. It is processed once the source container’s source_container.created event arrives. A dangling reference is fine; it just delays processing.
  • A message with no reference at all is stored but not processed.

A message’s source container is set once. Re-sending the message with a different reference does not move it.

Message payload

For message.created and message.updated, body.data looks like this:

{
    "data": {
        "source_name": "helphub",
        "source_message_id": "msg_8c41e",
        "source_message_type": "message",
        "created_at": "2026-03-09T15:30:00Z",
        "source_container": {
            "kind": "support_thread",
            "external_id": "48213"
        },
        "channel": {
            "source_name": "helphub",
            "server_name": "Acme (HelpHub)",
            "source_server_id": "acme",
            "channel_name": "Enterprise queue",
            "source_channel_id": "queue_enterprise"
        },
        "author": {
            "source_author_id": "cust_91b2",
            "display_name": "Priya Natarajan",
            "email": "priya@customer.example"
        },
        "content": "Still happening after clearing cookies. Every SSO user gets bounced back to the login page.",
        "source_url": "https://acme.helphub.example/tickets/48213#msg_8c41e"
    }
}

Required fields

Field Notes
source_name The source slug. Must match the source container’s source_name
source_message_id Your tool’s own stable ID for this message
source_message_type Upstream message classification, usually message. Use comment for issue comments if your tool distinguishes them
created_at When the message was written, from the source
source_container The source container reference. Accepted without it today, but not processed; becoming required (see below)
channel Where the message came from, see below
content The message text. May be an empty string if you send a non-empty title or at least one attachment

channel

channel describes the broader place a message came from and is used for filtering and display. It is coarser than the source container.

Source container kind Suggested channel
chat_channel The channel itself (source_channel_id = the channel ID)
support_thread The inbox, queue, or team the ticket lives in
issue The project, board, or repository
email_thread The mailbox
feedback_submission The board or category

channel.source_name must equal data.source_name.

author

author is optional, but a message without one contributes nothing to the people and companies Modem tracks. Send it whenever you can.

Field Notes
source_author_id Stable upstream ID for the person. Modem derives the author’s identity from it, so omit author entirely rather than falling back to a shared placeholder like "unknown", which would merge every unidentified author into one person
display_name
username
email Pass it through when your tool provides it. Modem uses email to recognize the same person across sources
is_bot true for automated authors (bots, integrations, your own app). Defaults to false
source_avatar_url

Optional fields

Field Notes
title Up to 500 characters. Useful for the first message in an issue or feedback submission
is_internal true for a note withheld from the external participant, such as a support agent’s private comment. Defaults to false
edited_at When the message was last edited at the source
attachments Images are OCR’d and their text is included in extraction
metadata Free-form JSON. Prefer structured fields here over embedding JSON in content
source_url Deep link to the message in your tool
source_thread_id, source_thread_type Threading inside the source container. See below
source_reply_to_id, source_reply_to_type The message this one replies to. See below

Threading and replies

Source containers scope the conversation; threads and replies describe structure inside it.

  • source_thread_id and source_thread_type identify the thread a message belongs to (a reply thread in a chat channel, for example). Modem treats distinct threads inside one source container as distinct concerns by default.
  • source_reply_to_id and source_reply_to_type identify the specific message this one replies to.

Each pair must be sent together or omitted together. For kinds where the source container is the thread (support_thread, issue, email_thread), you can omit both.

Examples

message.created

The first message in a chat channel source container:

{
    "event_type": "message.created",
    "created_at": "2026-03-09T15:30:01Z",
    "client": { "name": "@acme/chatterbox-sync", "version": "1.0.0" },
    "body": {
        "data": {
            "source_name": "chatterbox",
            "source_message_id": "1709854200000",
            "source_message_type": "message",
            "created_at": "2026-03-09T15:30:00Z",
            "source_container": {
                "kind": "chat_channel",
                "external_id": "ws_7f3a:ch_support_eng"
            },
            "channel": {
                "source_name": "chatterbox",
                "server_name": "Acme",
                "source_server_id": "ws_7f3a",
                "channel_name": "support-eng",
                "source_channel_id": "ch_support_eng"
            },
            "author": {
                "source_author_id": "u_2b7d",
                "display_name": "Jordan Lee",
                "email": "jordan@acme.example"
            },
            "content": "HelpHub #48213: enterprise customer can't log in after enabling SSO. Anyone seen the IdP key rotation issue before?",
            "source_url": "https://acme.chatterbox.example/ws_7f3a/ch_support_eng/1709854200000"
        }
    }
}

A comment on an issue source container:

{
    "event_type": "message.created",
    "created_at": "2026-03-10T11:02:03Z",
    "client": { "name": "@acme/bugtrack-sync", "version": "1.0.0" },
    "body": {
        "data": {
            "source_name": "bugtrack",
            "source_message_id": "cmt_77a0",
            "source_message_type": "comment",
            "created_at": "2026-03-10T11:02:00Z",
            "source_container": {
                "kind": "issue",
                "external_id": "iss_01HXK4M9"
            },
            "channel": {
                "source_name": "bugtrack",
                "server_name": "Acme (BugTrack)",
                "source_server_id": "acme",
                "channel_name": "Platform",
                "source_channel_id": "proj_platform"
            },
            "author": {
                "source_author_id": "usr_8a90",
                "display_name": "Sam Okafor",
                "email": "sam@acme.example"
            },
            "content": "Root cause: we cache the IdP signing cert for 24h. Rotation invalidates every session until the cache expires. Fix is to honor the JWKS cache-control header.",
            "source_url": "https://acme.bugtrack.example/ACME-1187#cmt_77a0"
        }
    }
}

message.updated

Same body as message.created, with the new content and an edited_at:

{
    "event_type": "message.updated",
    "created_at": "2026-03-09T15:45:05Z",
    "client": { "name": "@acme/chatterbox-sync", "version": "1.0.0" },
    "body": {
        "data": {
            "source_name": "chatterbox",
            "source_message_id": "1709854200000",
            "source_message_type": "message",
            "created_at": "2026-03-09T15:30:00Z",
            "edited_at": "2026-03-09T15:45:00Z",
            "source_container": {
                "kind": "chat_channel",
                "external_id": "ws_7f3a:ch_support_eng"
            },
            "channel": {
                "source_name": "chatterbox",
                "server_name": "Acme",
                "source_server_id": "ws_7f3a",
                "channel_name": "support-eng",
                "source_channel_id": "ch_support_eng"
            },
            "content": "HelpHub #48213: enterprise customer can't log in after enabling SSO. Started right after their IdP rotated its signing key."
        }
    }
}

message.deleted

{
    "event_type": "message.deleted",
    "created_at": "2026-03-09T16:00:01Z",
    "client": { "name": "@acme/chatterbox-sync", "version": "1.0.0" },
    "body": {
        "data": {
            "source_name": "chatterbox",
            "source_message_id": "1709854200000",
            "source_message_type": "message",
            "deleted_at": "2026-03-09T16:00:00Z",
            "source_container": {
                "kind": "chat_channel",
                "external_id": "ws_7f3a:ch_support_eng"
            },
            "channel": {
                "source_name": "chatterbox",
                "server_name": "Acme",
                "source_server_id": "ws_7f3a",
                "channel_name": "support-eng",
                "source_channel_id": "ch_support_eng"
            }
        }
    }
}

Processing behavior

  • message.created stores the message, runs OCR on image attachments, and queues the message for processing in its source container. Modem drains each source container serially: messages are read in order, in windows, with earlier concerns from the same source container available so restatements are merged rather than duplicated.
  • message.updated updates the stored content and OCRs newly added images. It does not re-run extraction for content that was already processed.
  • message.deleted marks the message deleted and clears its content.

Source container lifecycle events feed the same pipeline. When a source container’s state changes, that transition is recorded and used as evidence when Modem assesses the lifecycle of the topics the source container contributes to. See Why state matters.

Deduplication

source_nonce exists for one scenario: your upstream source gives you a delivery ID that is unique per delivery attempt, and that source may redeliver the same payload. Setting source_nonce to that delivery ID lets Modem drop the duplicate.

Good example: a webhook provider assigns every delivery a unique ID header and may redeliver the same webhook within seconds. Using that delivery ID as source_nonce is correct because it is scoped to the delivery, not to the underlying resource.

{
    "event_type": "message.created",
    "source_nonce": "a1f4b3c0-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
    "...": "..."
}

Bad example: building source_nonce from properties of the message (its ID, channel, or a combination). A message.updated with the same nonce as the original message.created would be silently dropped as a duplicate, and the update never applies.

// ❌ Do not do this. A future update to this message would be deduplicated away
{
    "event_type": "message.created",
    "source_nonce": "chatterbox:ch_support_eng:1709854200000",
    "...": "..."
}

How it works

When source_nonce is present, Modem generates a deterministic event ID from:

organization_id + source_name + source_nonce

A second request with the same nonce produces the same event ID and is treated as a duplicate delivery. The nonce must identify the delivery, not the resource. Without source_nonce, each request gets a unique event ID, which is the correct behavior for most integrations.

The same applies to source_container.* events.

Last updated on

Was this page helpful?