send_bot_message
Send a fixed bot message through an existing conversation.
send_bot_message(
context: dict,
text: str,
idempotency_key: str,
conversation_id: str | None = None,
) -> dictImmediately sends customer-visible text through an existing conversation's latest channel. The message is recorded as a bot-authored template message in the same conversation; it does not create a separate email thread and does not trigger an AI response.
This helper is available in recurring jobs, event handlers, and sidebar actions. It is intentionally unavailable in condition providers, system-prompt sections, sidebar widgets, and bot actions, where sending or closing while the bot is still deciding its response can create duplicate or contradictory replies.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
context | dict | Yes | The Python execution context |
text | str | Yes | Customer-visible text, from 1 to 5,000 characters |
idempotency_key | str | Yes | A stable, unique key for this logical message, from 1 to 200 characters |
conversation_id | str or None | No | Conversation UUID or public ID. Defaults to the current conversation; required in recurring jobs because they have no conversation context |
Returns
{
"success": True,
"conversationId": "conversation-uuid",
"messageId": "message-uuid",
"duplicate": False,
}duplicate is True when the same idempotency key already delivered the same text to the same conversation. Reusing a key with different text or another conversation raises an HTTP conflict instead of sending anything.
Safeguards
- The conversation must belong to the organization running the Python code.
- The conversation must still be open, bot-owned, non-imported, and outside the phone-call channel.
- The transcript must not change between validation and message creation. If it does, the call fails so the automation can reevaluate the latest customer message.
- Every message requires an idempotency key. Retries after a completed delivery return the original message instead of sending another copy.
- If delivery has an ambiguous failure, Octocom does not automatically resend. This avoids showing the customer a duplicate if the channel accepted the first attempt before timing out.
- Automation bot messages are limited to 100 per organization per minute, in addition to the shared REST API limit.
- A recurring job test executes the helper normally and sends a real message. Keep a
dry_runguard enabled while testing customer-facing jobs.
Recurring job example
Use a key derived from the durable business operation, not from the job run time. That keeps retries and overlapping runs safe.
def run_job(context):
item = organization_store_get("order-cancellations", "order-123")
if not item:
return {"processed": False}
cancellation = item["value"]
conversation_id = cancellation["conversationId"]
order_id = cancellation["orderId"]
# Perform and verify the refund, package cancellation, and any required
# subscription cancellation before telling the customer it succeeded.
result = send_bot_message(
context,
f"Your order {order_id} has been cancelled and fully refunded. "
"The refund will return to your original payment method.",
idempotency_key=f"order-cancelled:{order_id}:confirmation:v1",
conversation_id=conversation_id,
)
close_conversation(
context,
reason=f"Order {order_id} automatically cancelled and customer notified",
conversation_id=conversation_id,
)
return {"processed": True, "messageId": result["messageId"]}Do not close the conversation when the refund, package cancellation, subscription cancellation, or confirmation delivery fails. Leave it open for retry or manual follow-up.
Event handler example
Event handlers already have a current conversation, so conversation_id can be omitted:
def handle_event(context):
return send_bot_message(
context,
"We completed your requested account update.",
idempotency_key=f"account-update:{context['conversation']['id']}:v1",
)Related helpers
close_conversationcloses the thread after successful delivery.send_outbound_emailcreates a separate outbound email conversation instead.add_conversation_eventrecords an internal timeline event that the customer does not see.