Skip to main content

Overview

Your app can start and continue conversations in the inbox of workspace members, where they also talk with Twenty’s assistant. A message can end on a question, a form or a change to approve, which the member answers from the chat before the conversation continues with the assistant. The inbox helpers are exported from both twenty-sdk/logic-function and twenty-sdk/front-component, so a scheduled run and a button in your app post the same way.

Sending a message

sendInboxMessage() posts a message from your app in the chats of one or more workspace members and returns the conversation’s threadId.
src/logic-functions/notify-first-recording.ts
Key points:
  • workspaceMemberIds lists the members who receive the conversation, at least one. The first member of a new conversation owns it, and the others follow it as participants. A later send under the same key that lists new members adds them; members it leaves out keep the conversation.
  • threadKey picks the conversation for this app, shared by every member it is sent to. A new key starts a conversation titled title; a key you used before adds the message to that conversation.
  • idempotencyKey identifies the message within the conversation. Sending again with the same keys writes nothing new, so retries and concurrent runs never duplicate a message.
  • text is markdown, and can carry mentions and record references.
  • toolCall (optional) ends the message on one of:
    • a call the member answers, which pauses the conversation: ask_question (one multiple-choice question), request_form (fields to fill in) or propose_tool_call (a call to approve). Its input takes the toolName, the arguments you would call the tool with and a one-sentence summary. The tool must be one your app’s default role could run itself, such as create_one_person or update_one_opportunity (with the record id); an email (send_email or draft_email, with recipients, subject and a body in HTML) needs no permission of your app. The member reviews the call in the same card as a call the assistant proposes, edits it if they want, and the approved call runs with their own access;
    • one of your app’s own tools, by logicFunctionUniversalIdentifier, rendered by its front component with the input and output you pass. It does not pause the conversation.
  • Only one call the member answers can wait in a conversation at a time: sending another before they answer fails with THREAD_AWAITING_ANSWER. Plain messages and app tool calls are still accepted.
  • Only the member who owns a conversation can delete it, and a deleted conversation is gone for every member: it is not recreated, and nothing is added to it. The other members can unsubscribe to take it out of their own inbox.
  • Every member must have the AI permission, and the call fails without writing the message when one of them cannot have the conversation. Your app’s default role must grant SystemPermissionFlag.AI.

Who sends the message

A message is always written by your app, whatever triggered the call. The call itself runs like the default API clients:
  • In a logic function, it acts as the person who triggered the run, or as your application when nobody did, such as on a cron schedule.
  • In a front component, it acts as the member looking at the page. When the token has expired, the call refreshes it once through Twenty and retries.
The person behind the call needs the AI permission as well, like every recipient. To post from a front component, import the same helpers from twenty-sdk/front-component. Posting to the inbox shows how to open the conversation afterwards.

Mentions and record references

The inbox renders reference tokens in text as chips. Write them with these helpers rather than by hand:
src/logic-functions/flag-idle-deal.ts
  • inboxMention({ workspaceMemberId }) writes a mention of a workspace member, and inboxReferenceRecord({ objectNameSingular, recordId }) a chip that links to any record.
  • Both read the label the chip shows, as the composer does: the member’s full name, or their email when the name is empty, and the record’s label identifier, such as a company name. Brackets become parentheses and line breaks become spaces, so the chip always renders.
  • They read with the same access as the rest of the call, so that access needs to reach the member or record. A record reference makes two requests, one for the object’s label field and one for the record.
  • A mention only renders. It does not add the member to the conversation or notify them, so list everyone who should receive the message in workspaceMemberIds.
  • INBOX_ASSISTANT_MENTION is the text the composer writes when a member mentions the assistant (@Ada), so your messages name it the same way.