Skip to main content
Glama

send_message

Destructive

Send a message to ONE contact right now, outside any flow. For reaching many contacts use a broadcast instead. Target the contact with contactId (globally unique — preferred), or with platformId (the platform-side id, e.g. the Telegram user id). platformId is NOT globally unique: it is unique only per bot, so the same Telegram user talking to two of your bots is two contacts sharing one platformId. Pass botId alongside it whenever the application has more than one bot; without botId the call succeeds only if exactly one contact in the application matches, and otherwise fails listing the candidate bots. {{var|name}} placeholders in the text resolve against that contact's variable context. Requires the manage_broadcasts permission. Formatting: plain text by default, so tags arrive exactly as written. Pass parseMode "HTML" to send Telegram HTML (, , , , , , , , ; escape a literal <, > or & as &lt; &gt; &amp;); the flow tokens [[b]]…[[/b]], [[i]], [[u]] and [[a|url]]…[[/a]] work too. Values filled in from what people typed are escaped, so they show as text and never add formatting. HTML that Telegram cannot parse is sent once more as plain text with the tags removed. Telegram bots and web chat only (web chat: not with media); refused for Instagram, mail, Telegram user accounts and SDK bots. Markdown is never rendered. Media: pass up to 10 attachments as publicly reachable http(s) URLs; the text becomes the caption (max 1024 characters) and may be empty. Several attachments send as one album. The kind is inferred from the URL's file extension — override with type when the URL has none. Not supported for SDK bots or for Telegram user-account bots (telegram_mtproto, text only) — the call is rejected and nothing is sent. Buttons: up to 8, each carrying EXACTLY ONE destination — a url (http(s) or tg://) the recipient opens, or a flowId, an already-applied INTERACTIVE flow that runs for that recipient when they tap it. The two kinds mix freely in one keyboard, so a custom text with flow-wired buttons needs no wrapper flow. A flow button starts its flow from the start block with the recipient's own variable context, and re-runs on every tap. Broadcast and operation flows are rejected, as are flow buttons on SDK bots (taps never reach the runtime there). Buttons of either kind are rejected for telegram_mtproto bots: a user account cannot send buttons, so put the link in the text. To send a WHOLE flow as the message instead of wiring one behind a button, use send_flow_to_contacts. Buttons cannot be combined with media; send those as two messages. Delivery is asynchronous: a successful response means the bot accepted the send, not that the platform delivered it. A send that fails afterwards (a broken media URL, a contact that blocked the bot, a chat not found) is not returned here: it is written to the flow logs as an error row for that contact with action direct_message (read it with query_flow_logs or get_contact_activity), and no outgoing message is added to the transcript. Unsubscribed contacts and inactive bots are rejected. A telegram_mtproto bot (a Telegram user account) sends nothing until its owner turns on "Sending from this account" in the dashboard (Bot settings → Account). It is OFF for every newly connected account and no tool changes it. While it is off the call is rejected, except to the account's own Saved Messages (platformId "me"). NOT idempotent and not reversible — each call sends another message to a real person, and a sent message cannot be recalled. Confirm the recipient and text with the user before calling, and never retry a timed-out call blindly.

Input Schema

NameRequiredDescriptionDefault
textNoMessage body. Required unless media is passed; with media it becomes the caption (max 1024 characters) and may be omitted. `{{var|name}}` placeholders resolve against the recipient's variable context. Plain text unless parseMode is "HTML".
botIdNoBot to send from. Required in practice when targeting by platformId in a multi-bot application; without it the call succeeds only if exactly one contact matches, and otherwise fails listing the candidate bots.
mediaNoUp to 10 attachments. Several items send as one album with `text` as the shared caption.
buttonsNoUp to 8 buttons, rendered one per row under the message. Each needs exactly one of url or flowId; the two kinds may be mixed. Rejected together with media.
contactIdNoPreferred way to target the recipient: the globally unique FlowCastle contact id. Supply either this or platformId.
parseModeNoSet to "HTML" to send the text (or caption) as Telegram HTML. Omit for plain text.
platformIdNoPlatform-side user id (e.g. the Telegram user id). NOT globally unique — unique only per bot — so pass botId alongside it when the application has more than one bot.
applicationIdNoApplication (workspace) id. Optional: an application-scoped key (app_...) defaults to its own application, but a personal key (usr_...) has no default and omitting it fails with MCP_APPLICATION_REQUIRED. Call list_applications to get the id.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed2 schema fields changed
    • addedInput schema / properties / parseMode
      Added value: +{
      +  "description": "Set to \"HTML\" to send the text (or caption) as Telegram HTML. Omit for plain text.",
      +  "enum": [
      +    "HTML"
      +  ],
      +  "type": "string"
      +}
    • changedInput schema / properties / text / description
      Previous value: -"Message body. Required unless media is passed; with media it becomes the caption (max 1024 characters) and may be omitted. `{{var|name}}` placeholders resolve against the recipient's variable context."New value: +"Message body. Required unless media is passed; with media it becomes the caption (max 1024 characters) and may be omitted. `{{var|name}}` placeholders resolve against the recipient's variable context. Plain text unless parseMode is \"HTML\"."
  2. Changed4 schema fields changed
    • changedInput schema / properties / buttons / description
      Previous value: -"Up to 8 URL buttons, rendered one per row under the message. Rejected together with media."New value: +"Up to 8 buttons, rendered one per row under the message. Each needs exactly one of url or flowId; the two kinds may be mixed. Rejected together with media."
    • addedInput schema / properties / buttons / items / properties / flowId
      Added value: +{
      +  "description": "Id of an already-applied INTERACTIVE flow to run for the recipient when they tap the button (from apply_actions' idRemap or get_application_context). Broadcast and operation flows are rejected. Set this OR url, never both.",
      +  "type": "string"
      +}
    • changedInput schema / properties / buttons / items / properties / url / description
      Previous value: -"Required. Where the button leads — http(s) or tg:// only. There is no callback/flow-wired button here; use send_flow_to_contacts for those."New value: +"Where the button leads — http(s) or tg:// only. Set this OR flowId, never both."
    • changedInput schema / properties / buttons / items / required
      Previous value: -[
      -  "text",
      -  "url"
      -]New value: +[
      +  "text"
      +]
  3. Changed1 schema field changed
    • addedInput schema / properties / buttons
      Added value: +{
      +  "description": "Up to 8 URL buttons, rendered one per row under the message. Rejected together with media.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "text": {
      +        "description": "Required. Label shown on the button.",
      +        "type": "string"
      +      },
      +      "url": {
      +        "description": "Required. Where the button leads — http(s) or tg:// only. There is no callback/flow-wired button here; use send_flow_to_contacts for those.",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "text",
      +      "url"
      +    ],
      +    "type": "object"
      +  },
      +  "maxItems": 8,
      +  "type": "array"
      +}
  4. Changed4 schema fields changed
    • addedInput schema / properties / media
      Added value: +{
      +  "description": "Up to 10 attachments. Several items send as one album with `text` as the shared caption.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "filename": {
      +        "description": "Filename shown for DOCUMENT sends. Defaults to the URL's basename.",
      +        "type": "string"
      +      },
      +      "type": {
      +        "description": "Attachment kind. Inferred from the URL's file extension when omitted; unrecognized extensions fall back to DOCUMENT.",
      +        "enum": [
      +          "IMAGE",
      +          "IMAGE_URL",
      +          "VIDEO",
      +          "VIDEO_NOTE",
      +          "DOCUMENT",
      +          "VOICE",
      +          "STICKER"
      +        ],
      +        "type": "string"
      +      },
      +      "url": {
      +        "description": "Required. Publicly reachable http(s) URL of the file; the platform downloads it from there.",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "url"
      +    ],
      +    "type": "object"
      +  },
      +  "maxItems": 10,
      +  "type": "array"
      +}
    • changedInput schema / properties / text / description
      Previous value: -"Required. Plain-text message body, must be non-empty. `{{var|name}}` placeholders resolve against the recipient's variable context."New value: +"Message body. Required unless media is passed; with media it becomes the caption (max 1024 characters) and may be omitted. `{{var|name}}` placeholders resolve against the recipient's variable context."
    • removedInput schema / properties / text / minLength
      Removed value: -1
    • removedInput schema / required
      Removed value: -[
      -  "text"
      -]
  5. Changed5 schema fields changed
    • addedInput schema / properties / applicationId / description
      Added value: +"Application (workspace) id. Optional: an application-scoped key (app_...) defaults to its own application, but a personal key (usr_...) has no default and omitting it fails with MCP_APPLICATION_REQUIRED. Call list_applications to get the id."
    • addedInput schema / properties / botId / description
      Added value: +"Bot to send from. Required in practice when targeting by platformId in a multi-bot application; without it the call succeeds only if exactly one contact matches, and otherwise fails listing the candidate bots."
    • addedInput schema / properties / contactId / description
      Added value: +"Preferred way to target the recipient: the globally unique FlowCastle contact id. Supply either this or platformId."
    • addedInput schema / properties / platformId / description
      Added value: +"Platform-side user id (e.g. the Telegram user id). NOT globally unique — unique only per bot — so pass botId alongside it when the application has more than one bot."
    • addedInput schema / properties / text / description
      Added value: +"Required. Plain-text message body, must be non-empty. `{{var|name}}` placeholders resolve against the recipient's variable context."
  6. Added

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Far exceeds the annotations: discloses async delivery semantics, that later failures are written to flow logs under action `direct_message` rather than returned, the manage_broadcasts permission requirement, the telegram_mtproto "Sending from this account" toggle being OFF by default and unchangeable by tools, HTML escaping rules and the plain-text fallback, and the irreversibility warning. Annotations only flag destructive/non-idempotent; the description adds the operational detail an agent actually needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and the sibling alternative before dense detail. Nearly every clause carries information, but the block is very long and stacks formatting, media, buttons, delivery and account-restriction topics without clear sectioning, so it is harder to scan than it needs to be.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema, but the description covers return semantics (success means accepted, not delivered), the failure-reporting channel (query_flow_logs / get_contact_activity), permissions, channel restrictions and non-idempotency. Nothing an agent needs in order to call it safely is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description adds real meaning: platformId uniqueness is per-bot with the candidate-bots failure mode, botId's role, parseMode's exact token set and escaping rules, and flowId requiring an already-applied INTERACTIVE flow with broadcast/operation flows rejected. Some of this duplicates the schema wording, which keeps it below 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource with scope: "Send a message to ONE contact right now, outside any flow." Immediately distinguishes from the broadcast alternative and later from send_flow_to_contacts, so an agent can differentiate it from siblings without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit routing to alternatives ("For reaching many contacts use a broadcast instead", "To send a WHOLE flow... use send_flow_to_contacts"), explicit incompatibilities ("Buttons cannot be combined with media; send those as two messages"), and explicit when-not (refused for Instagram, mail, Telegram user accounts, SDK bots). Covers precedence between contactId and platformId plus botId disambiguation.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.