Wire Discord slash commands and interactions to MultiMail's approval queue so every bot-triggered email send goes through the right gate before delivery.
Discord bots have become a standard interface for internal team workflows — support queues, moderation actions, member onboarding, and outreach requests all flow through bot commands. When those commands need to trigger outbound email, the gap between "bot says send" and "email actually delivered" is where mistakes happen.
MultiMail sits between your Discord bot and your email infrastructure. A slash command fires, the bot calls MultiMail's REST API, and depending on your oversight mode, the message either queues for human approval or sends immediately with a notification. Either way, you get a full audit trail of who triggered what and when.
This pattern is common in community management tools, support bots, and internal tooling where a Discord interaction is the user-facing trigger but the actual effect — an email to a customer, a partner, or a mailing list — needs to be controlled and traceable.
Discord commands make email outreach dangerously easy. MultiMail's gated_send mode holds every bot-triggered send in a review queue. A human approves or rejects via the MultiMail dashboard or API before any message leaves your domain.
MultiMail checks recipients against your contact list and domain policy before queuing a send. A typo in a Discord command won't result in email to an unintended address.
Every send, approval, and rejection is logged with the originating interaction ID. You can trace any email back to the exact Discord command that triggered it, including the user, channel, and timestamp.
Discord bots can query MultiMail inboxes via the REST API without needing direct IMAP or SMTP credentials. The bot gets read access scoped to specific mailboxes with no path to credential leakage.
MultiMail can post delivery confirmations, bounce alerts, and approval requests back to specific Discord channels via webhooks, closing the loop without requiring the bot to poll.
import { Client, GatewayIntentBits, SlashCommandBuilder } from 'discord.js';
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName !== 'sendemail') return;
const to = interaction.options.getString('to', true);
const subject = interaction.options.getString('subject', true);
const body = interaction.options.getString('body', true);
await interaction.deferReply({ ephemeral: true });
const response = await fetch('https://api.multimail.dev/v1/send_email', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MULTIMAIL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: '[email protected]',
to,
subject,
body,
oversight_mode: 'gated_send',
metadata: {
discord_user: interaction.user.tag,
discord_channel: interaction.channelId,
discord_guild: interaction.guildId,
},
}),
});
const result = await response.json();
if (result.status === 'queued') {
await interaction.editReply(
`Email queued for approval (ID: \`${result.message_id}\`). A reviewer will approve or reject it in the MultiMail dashboard.`
);
} else {
await interaction.editReply(`Failed to queue email: ${result.error}`);
}
});
client.login(process.env.DISCORD_TOKEN);A /sendemail slash command handler that calls MultiMail's send_email endpoint in gated_send mode. The email is held until a human approves it in the MultiMail dashboard.
import { Client, GatewayIntentBits } from 'discord.js';
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName !== 'checkinbox') return;
const mailbox = interaction.options.getString('mailbox') ?? '[email protected]';
await interaction.deferReply({ ephemeral: true });
const response = await fetch(
`https:"cm">//api.multimail.dev/v1/check_inbox?mailbox=${encodeURIComponent(mailbox)}&limit=5`,
{
headers: {
'Authorization': `Bearer ${process.env.MULTIMAIL_API_KEY}`,
},
}
);
const { emails } = await response.json();
if (!emails.length) {
await interaction.editReply(`No unread messages in ${mailbox}.`);
return;
}
const lines = emails.map((e) =>
`• **${e.subject}** — from \`${e.from}\` (${new Date(e.received_at).toLocaleString()})`
);
await interaction.editReply(
`**${emails.length} unread in ${mailbox}:**\n${lines.join('\n')}`
);
});
client.login(process.env.DISCORD_TOKEN);A /checkinbox command that calls MultiMail's check_inbox endpoint and posts a summary of recent messages back to the user in Discord.
import express from 'express';
const app = express();
app.use(express.json());
app.post('/webhooks/multimail', async (req, res) => {
const event = req.body;
if (event.type !== 'message.pending_approval') {
return res.json({ ok: true });
}
const { message_id, to, subject, from, metadata } = event.data;
const discordPayload = {
content: '**Email approval required**',
embeds: [
{
title: subject,
fields: [
{ name: 'From', value: from, inline: true },
{ name: 'To', value: to, inline: true },
{ name: 'Triggered by', value: metadata?.discord_user ?? 'unknown', inline: true },
{ name: 'Message ID', value: `\`${message_id}\``, inline: false },
],
description: `Approve at https:"cm">//app.multimail.dev/approvals/${message_id}`,
color: 0xf59e0b,
},
],
};
await fetch(process.env.DISCORD_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(discordPayload),
});
res.json({ ok: true });
});
app.listen(3000);A lightweight HTTP handler that receives MultiMail approval webhooks and posts a formatted message to a Discord channel with approve/reject context.
import os
import discord
from discord.ext import commands
import httpx
bot = commands.Bot(command_prefix=&"cm">#039;!')
MULTIMAIL_BASE = &"cm">#039;https://api.multimail.dev/v1'
HEADERS = {&"cm">#039;Authorization': f'Bearer {os.environ["MULTIMAIL_API_KEY"]}'}
@bot.command(name=&"cm">#039;reply')
async def reply_email(ctx, thread_id: str, *, reply_body: str):
"""Reply to an email thread: !reply <thread_id> <body>"""
async with httpx.AsyncClient() as client:
resp = await client.post(
f&"cm">#039;{MULTIMAIL_BASE}/reply_email',
headers=HEADERS,
json={
&"cm">#039;thread_id': thread_id,
&"cm">#039;body': reply_body,
&"cm">#039;oversight_mode': 'gated_send',
&"cm">#039;metadata': {
&"cm">#039;discord_user': str(ctx.author),
&"cm">#039;discord_channel': str(ctx.channel.id),
},
},
)
result = resp.json()
if result.get(&"cm">#039;status') == 'queued':
await ctx.reply(f&"cm">#039;Reply queued for approval (`{result["message_id"]}`). Awaiting review.')
else:
await ctx.reply(f&"cm">#039;Error: {result.get("error", "unknown")}')
bot.run(os.environ[&"cm">#039;DISCORD_TOKEN'])A Python Discord bot command that fetches a thread from MultiMail and posts a reply, keeping the full email context in the MultiMail audit log.
Sign up at multimail.dev and create a mailbox (e.g., [email protected] or a @multimail.dev address). Copy your API key from the dashboard — it starts with mm_live_ for production or mm_test_ for test mode.
curl -X POST https://api.multimail.dev/v1/create_mailbox \
-H &"cm">#039;Authorization: Bearer $MULTIMAIL_API_KEY...' \
-H &"cm">#039;Content-Type: application/json' \
-d &"cm">#039;{"address": "[email protected]"}'Install the Discord library and register the slash commands your bot will expose. Use the Discord Developer Portal to create an application and bot token.
npm install discord.js
"cm"># Register a /sendemail slash command via the Discord REST API
curl -X POST https://discord.com/api/v10/applications/$APP_ID/commands \
-H "Authorization: Bot $DISCORD_TOKEN" \
-H "Content-Type: application/json" \
-d &"cm">#039;{
"name": "sendemail",
"description": "Queue an email for approval",
"options": [
{"name": "to", "description": "Recipient address", "type": 3, "required": true},
{"name": "subject", "description": "Email subject", "type": 3, "required": true},
{"name": "body", "description": "Email body", "type": 3, "required": true}
]
}&"cm">#039;In your bot's interactionCreate handler, call MultiMail's send_email endpoint with oversight_mode set to gated_send. Pass Discord metadata so every queued message is traceable back to its source.
const response = await fetch(&"cm">#039;https://api.multimail.dev/v1/send_email', {
method: &"cm">#039;POST',
headers: {
&"cm">#039;Authorization': `Bearer ${process.env.MULTIMAIL_API_KEY}`,
&"cm">#039;Content-Type': 'application/json',
},
body: JSON.stringify({
from: &"cm">#039;[email protected]',
to: interaction.options.getString(&"cm">#039;to'),
subject: interaction.options.getString(&"cm">#039;subject'),
body: interaction.options.getString(&"cm">#039;body'),
oversight_mode: &"cm">#039;gated_send',
metadata: { discord_user: interaction.user.tag },
}),
});Register a webhook URL in the MultiMail dashboard under Settings > Webhooks. Point it at your bot's HTTP endpoint. When an email is approved, rejected, or bounces, MultiMail will POST an event that your bot can relay back to the originating Discord channel.
"cm"># In your MultiMail dashboard, register:
"cm"># Webhook URL: https://yourbot.yourdomain.com/webhooks/multimail
"cm"># Events: message.pending_approval, message.delivered, message.bounced
"cm"># Then in your bot server:
app.post(&"cm">#039;/webhooks/multimail', (req, res) => {
const { type, data } = req.body;
if (type === &"cm">#039;message.delivered') {
channel.send(`Email ${data.message_id} delivered to ${data.to}`);
}
res.json({ ok: true });
});Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 38-tool MCP server. Formally verified in Lean 4.