Discord Module
Build Discord bots with BPMN: a slash command starts a Session, the bot replies in the channel, with embeds, buttons and files.
The Discord Module adds a Discord start event and Discord behavior for the Output, Input and End Tasks on the DiscordChannel Channel, plus a DiscordRichMessage Task for embeds and components and a DiscordNotification Task that posts to a channel from any flow. When a user runs your bot's slash command (/ask by default) in a channel, thread or direct message, a Session starts at the Discord start, with the command in {discord}. It requires Environment configuration: the Discord application's bot token, application id and public key.
How it works
Discord does not send a bot the messages people write unless the bot holds a permanent gateway connection, which a web service running on several instances cannot. What Discord does deliver over HTTPS are interactions: slash commands, button clicks, select menus and modal (text box) submissions, each signed with the application's public key. The Discord Module is built on them:
- A user runs the Environment's slash command (
/ask how do I reset my password?). If the conversation (see Session Mode) has no live Session, one starts at the Process's Discord start, with the command in{discord}; the first Input Task waits for the user's next reply. If the conversation has a live Session, the command continues it: the waiting Input receives its text. - An Input Task posts its prompt with a Reply button. Clicking it opens a text box; what the user types resumes the Session. The user can also answer by running the command again, or by clicking a button or choosing in a select menu of a DiscordRichMessage posted in the Session (the Input receives the component's
custom_id, or the chosen values). - Discord shows "thinking…" after a command or a reply until the bot answers; the Session's next Output (or Input prompt) fills that reply. Every other message is a plain message in the channel. Nobody is ever pinged by a message's text:
@everyone,@here, roles and users stay plain text.
The webhook answers Discord at once and the Session runs a moment later, so Discord never times out waiting. A start that cannot run (a plan limit, a Process without a Discord start) frees the conversation within 15 minutes. A Session keeps its conversation until it ends, or until 24 hours pass without an interaction; after that, or when the Session has ended, the Reply button tells the user to run the command again.
Configuration
The Discord Module is configured per Environment: each Environment supplies its own Discord application, so Development and Production can each talk to their own bot. Set these values in the Module's Environment configuration.
| Field | Type | Required | Description |
|---|---|---|---|
| Bot Token | string | Yes | The bot token from the Developer Portal (Bot > Reset Token). Stored securely. |
| Application ID | string | Yes | The application's id (General Information). Registers the slash command and completes interaction replies. |
| Public Key | string | Yes | The application's public key (General Information), 64 hex characters. Every interaction is verified against it. |
| Command | string | No | Slash command: the command that starts a Session, without the slash. 1-32 lowercase letters, digits, - or _. Default: ask. |
| Command description | string | No | Shown by Discord next to the command. Default: Start a conversation. |
| Server (guild) ID | string | No | Registers the command in that server only, where it appears at once. Empty registers it globally for every server the bot is in; Discord can take up to an hour to show a new global command. |
| Session Mode | enum | No | Sessions: PerUserPerChannel (one user in a channel or thread; two people using the command in the same channel get separate Sessions) or PerChannel (a whole channel or thread). Default: PerUserPerChannel. |
| Default Channel | string | No | Channel ID the DiscordNotification and DiscordRichMessage Tasks post to when their Channel is empty outside a Discord Session. |
Connect your Discord application
In the Discord Developer Portal:
- New Application; on General Information copy the Application ID and the Public Key into the configuration.
- Bot: Reset Token and paste the token into Bot Token. No privileged gateway intents are needed.
- General Information > Interactions Endpoint URL:
https://<your engine host>/api/DiscordModule/interactions/<environment token>(the token of the Environment the Process is deployed to). The Module's Environment configuration shows this address ready to copy under Interactions endpoint. Save the configuration before saving this URL: Discord checks the endpoint once with a signed ping and a badly signed request, and the Module answers them with the public key already set. - OAuth2 > URL Generator: scopes
botandapplications.commands; bot permissions View Channel, Send Messages, Send Messages in Threads, Embed Links, Attach Files, Read Message History. Open the generated URL to invite the bot to your server. - Deploy a Process with a Discord start to the Environment. The deploy registers the slash command (the deploy log says where); a global command can take up to an hour to appear in Discord, a server command appears at once.
Enable User Install under Installation too if you want people to use the command in direct messages with the bot.
Permissions
The bot posts with the permissions you granted at the invite; a channel the bot cannot see or write in stops the Task with error 4008 (ModuleMissingConfiguration) and a message naming the permissions. Files need Attach Files, embeds Embed Links. The size limit of attachments depends on the server's boost level (10 MB without a boost); the Module caps a message at 10 files and 15 MB.
Tasks
The Discord Module provides a Discord start, channel-specific behavior for the standard I/O Tasks defined by the Standard Module, a DiscordRichMessage Task for embeds and components and a DiscordNotification Task that posts to a channel from any flow. The property panel for the standard Tasks is identical to the chat versions; only the runtime behavior differs.
Discord start
The start event of a Discord conversation. Use it as the Process's start (or one of its starts, next to a Chat start or a Slack start: the engine runs the start that serves the incoming Channel). One Discord start per Process.
Properties
| Property | Type | Required | Example | Description |
|---|---|---|---|---|
| Discord variable | variable | No | {discord} | Receives the command that started the Session (see below). Default: {discord}. |
The {discord} variable
| Path | Example | Description |
|---|---|---|
{discord.text} | how do I reset my password? | What the user typed after the command (its text option); empty when they typed nothing. |
{discord.files} | The file attached to the command (its file option): {discord.files.count}, {discord.files.names}, {discord.files.0} (for the Read file Task or an Output's Attachments). | |
{discord.from} | 123456789012345678 | The Discord user who ran the command (also {execution.channelSender}). |
{discord.profileName} | Maria | The user's display name: server nickname, else global name, else username. |
{discord.to} | 234567890123456789 | The channel (a thread is a channel of its own) the Session replies to. |
{discord.guild} | 345678901234567890 | The server, or dm for a direct message to the bot. |
{discord.command} | ask | The slash command the user ran. |
{discord.messageId} | 456789012345678901 | Discord's id of the interaction. |
{discord.channel} | DiscordChannel | The Session's Channel. |
{discord.isTest} | false | True when the start ran in the test chat. |
Behavior
- The Environment's slash command, run where no Session is live (see How it works), starts one on the
DiscordChannelChannel at this event. - The Session is bound to the channel and user, and the command's text is the first entry of the conversation history.
- The command's file is downloaded from Discord and saved like an InputTask's Store files in. A file that cannot be kept (too large, storage full) is noted in the Session log and the Session starts anyway.
{discord}is written and the Token moves on at once. The first Output fills the "thinking…" reply Discord shows; the first InputTask posts its prompt with a Reply button and waits for the user's reply.
Testing from the chat. Start a chat on the Environment in the editor (or with the assistant's chat_start_session tool, giving test data such as { "text": "hi", "from": "123", "profileName": "Maria" }). With a text, {discord} is written from the test data at once (from, to, guild, profileName, command are optional); without one, the Discord start asks for the message and your next chat message (and the file you attach) becomes {discord.text} / {discord.files}. {discord.isTest} is true, and the rest of the flow runs on the chat. On any other Channel (a call, a form) the Discord start stops the Session with error 8025 (StartWrongChannel).
Example
A user types /ask I need help with my invoice in #support. A Session starts, {discord.text} holds the text and {discord.profileName} the user's name, and the Token moves on, typically to an OutputTask replying Hi {discord.profileName}, let me look into it., which appears as the command's reply.
OutputTask (Channel: DiscordChannel)
Posts a text message, and optionally files, to the conversation's channel.
Properties
| Property | Type | Required | Example | Description |
|---|---|---|---|---|
| Output Message | string | Yes | Hi {discord.profileName}, your ticket is open. | The text posted to Discord (Discord markdown). Supports {variable} placeholders. |
| Attachments | file field | No | ref-session:{report}, ref-storage:terms.pdf | Files posted with the message, separated by commas: Session files (from the Standard Module's Create file Task, or a file list such as an InputTask's Store files in) or saved files. See File fields. |
Behavior
- The Task resolves every
{variable}placeholder against the current Session variables. - Mentions in the resolved text never ping anyone:
@everyone,@here, roles and users are posted as plain text. - If Attachments is set, the files are read first: at most 10 files and 15 MB per message. A file that is too large, missing or not a file stops the Task with an error, and nothing is posted.
- The first message after a command or a reply fills the "thinking…" reply Discord is showing; otherwise the Task posts a message in the channel. Text longer than 2000 characters is posted in parts, the files with the last part.
- The conversation history of the Session lists the files with the message.
- If Discord rejects the message or the files, the Session records an error instead of continuing silently.
InputTask (Channel: DiscordChannel)
Posts a prompt with a Reply button and waits for the user's reply.
Properties
| Property | Type | Required | Example | Description |
|---|---|---|---|---|
| Input Message | string | Yes | Which account is it about? | The prompt posted to Discord. Supports {variable} placeholders. |
| Variable | variable | Yes | {account} | The variable that receives the reply. |
| Store files in | variable | No | {docs} | A file list with the files of the reply (a slash command run with a file), downloaded and saved. Without it, files are only listed in the conversation history. |
Behavior
- The prompt is posted (filling the "thinking…" reply when one is owed) with a Reply button under it.
- The Session waits. The user clicks Reply and types in the text box, runs the slash command again, or clicks a button / chooses in a select menu of a DiscordRichMessage posted in the Session.
- The reply's text (or the component's
custom_id, or the chosen values separated by commas) is written to the Variable and added to the conversation history. Discord shows "thinking…" after a text reply until the Session's next Output or Input fills it.
EndTask (Channel: DiscordChannel)
Ends the Session: frees the conversation, so the user's next command starts a new Session, and removes a "thinking…" reply the flow never filled. A closing message is an OutputTask before the End event.
DiscordRichMessage
Posts a message with embeds (title, description, fields, colour, image) and components (buttons, select menus): the Discord features the shared Output can't express. In a Discord Session with an empty Channel it replies in the conversation; from any other flow (a form, a call, a schedule) it posts to Channel, or to the Default Channel of the configuration.
Properties
| Property | Type | Required | Example | Description |
|---|---|---|---|---|
| Channel | string | No | 234567890123456789 | Empty: in a Discord Session, the conversation; in any other flow, the Default Channel. Or a channel ID. |
| Text | string | No | Here is your order | Optional plain text above the embeds (up to 2000 characters). |
| Embeds JSON | json | No | [{"title":"Order {orderId}","description":"Ready","color":5793266}] | The embeds: a JSON array, a single embed object, or { "embeds": [...] }. Up to 10. Text or Embeds JSON is required. |
| Components JSON | json | No | [{"type":1,"components":[{"type":2,"style":1,"label":"Yes","custom_id":"yes"}]}] | Action rows with buttons or select menus: a JSON array of rows, a single row, or { "components": [...] }. Up to 5 rows. |
Behavior
{variables}inside the JSON are inserted as escaped JSON text: a user's input cannot add embeds, buttons or links. A template that is a single{variable}and nothing else is used as the JSON itself (embeds built by a Script task).- Invalid JSON, more than 10 embeds or more than 5 rows stop the Task with an error; nothing is posted.
- In a Session, the message fills the "thinking…" reply when one is owed, and the conversation history records its text or first title. A click on one of its buttons (or a choice in a select menu) reaches the waiting InputTask as the component's
custom_id(or the chosen values).
Discord's embed and component formats are documented at discord.com/developers/docs.
DiscordNotification
Posts a message, optionally with files, to a channel from any flow (form, voice, schedule, chat), with the Environment's bot token.
Properties
| Property | Type | Required | Example | Description |
|---|---|---|---|---|
| Channel | string | No | 234567890123456789 | Channel ID. Empty uses the Default Channel of the configuration. The bot needs View Channel and Send Messages there. |
| Message | string | Yes | **New lead**: {form.fields.email} | Discord markdown with {variable} placeholders. Longer than 2000 characters is posted in parts. |
| Attachments | file field | No | ref-session:{report} | Files posted with the message (with the last part), separated by commas. |
Behavior
- The Channel (or Default Channel) and the Message are resolved; without a channel or with an empty message the Task stops with an error.
- The files are read first; the message is posted with mentions disabled, the files with it.
- A refusal by Discord (missing access or permissions, a file too large for the server) routes the Task's error branch; the flow goes on.
Limits and notes
- Only interactions reach the bot: a message typed in the channel without the slash command, without a Reply button or without a component does nothing. Tell your users to use
/ask(or the configured command). - Discord's "thinking…" state lasts at most 15 minutes. A Session that takes longer to answer posts its reply as a plain message instead.
- Slash command names must be lowercase; a global command can take up to an hour to appear, a server command appears at once.
- Two Environments whose bots are in the same channel hold separate Sessions: the conversation key includes the Environment.