This doc is designed for your coding agent. Share it with your agent so it learns how to send files back to Discord.
Example prompt:
Read docs/sendfiles.md from OpenAB GitHub and send the file back to my Discord thread.💡 Tip: If it works the first time, ask your agent to save this as a SKILL so it remembers how to do it next time without re-reading the doc.
OpenAB streams text only — it does not relay file attachments from the agent. To send a file back to the user, the agent must call the Discord API directly.
For image-specific guidance (formats, sidecar pattern), see sendimages.md.
┌──────────┐ text only ┌──────────┐ ACP stdio ┌──────────────┐
│ Discord │◄────────────│ OpenAB │◄────────────│ Agent (CLI) │
│ Thread │ └──────────┘ └──────┬───────┘
│ │ │
│ │ Discord REST API │
│ │◄─────────────────────────────────────────────┘
│ │ POST /channels/{thread_id}/messages
│ │ + multipart file attachment
└──────────┘
┌──────────┐ text only ┌──────────┐ ACP stdio ┌──────────────┐
│ Discord │◄────────────│ OpenAB │◄────────────│ Agent (CLI) │
│ Thread │ └──────────┘ └──────┬───────┘
│ │ │
│ │ send presigned URL as message │ upload file
│ │◄─────────────────────────────────────────────┤─────────────►┌─────┐
│ │ POST /channels/{thread_id}/messages │ │ S3 │
└─────┬────┘ │ │ R2 │
│ │ │ GCS │
│ user clicks link │ └──┬──┘
└────────────────────────────────────────────────────────────────────►│
presigned GET │
◄─────────────────────────────────────────────┘
OpenAB only streams text via ACP. To send a file, the agent calls the
Discord API directly using the thread_id from sender_context.
Every message includes a <sender_context> JSON block:
{
"schema": "openab.sender.v1",
"channel": "discord",
"channel_id": "1490282656913559673",
"thread_id": "1499442140172910654"
}Use thread_id as the target. Fall back to channel_id if thread_id is absent.
The agent needs a Discord Bot Token to call the API. Pass it via [agent] env in config.toml:
[agent]
env = { DISCORD_BOT_TOKEN = "${DISCORD_BOT_TOKEN}" }
⚠️ The token in[discord] bot_tokenis consumed by OpenAB itself and is not automatically forwarded to the agent subprocess. You must explicitly pass it via[agent] env.
For production, consider creating a dedicated "File Deliverer" bot with minimal permissions instead of sharing the main OAB bot token. See sendimages.md — Security: dedicated bot recommended for the full architecture diagram and comparison table.
# Production: dedicated file-upload bot (e.g. "File Deliverer")
[agent]
env = { DISCORD_FILE_BOT_TOKEN = "${DISCORD_FILE_BOT_TOKEN}" }For personal use or small teams, sharing the same token is fine — you already trust your agent.
POST https://discord.com/api/v10/channels/{thread_id}/messages
Authorization: Bot {DISCORD_BOT_TOKEN}
Content-Type: multipart/form-data
curl -X POST "https://discord.com/api/v10/channels/${THREAD_ID}/messages" \
-H "Authorization: Bot ${DISCORD_BOT_TOKEN}" \
-F "content=Here is the report" \
-F "files[0]=@/path/to/report.pdf"Discord supports up to 10 attachments per message:
curl -X POST "https://discord.com/api/v10/channels/${THREAD_ID}/messages" \
-H "Authorization: Bot ${DISCORD_BOT_TOKEN}" \
-F "content=Build artifacts" \
-F "files[0][email protected]" \
-F "files[1][email protected]"import os, requests
def send_file(thread_id: str, file_path: str, message: str = ""):
url = f"https://discord.com/api/v10/channels/{thread_id}/messages"
headers = {"Authorization": f"Bot {os.environ['DISCORD_BOT_TOKEN']}"}
with open(file_path, "rb") as f:
requests.post(url, headers=headers,
data={"content": message},
files={"files[0]": (os.path.basename(file_path), f)})const fs = require("fs");
const FormData = require("form-data");
async function sendFile(threadId, filePath, message = "") {
const form = new FormData();
form.append("content", message);
form.append("files[0]", fs.createReadStream(filePath));
await fetch(`https://discord.com/api/v10/channels/${threadId}/messages`, {
method: "POST",
headers: { Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN}` },
body: form,
});
}| Server Boost Level | Max Upload |
|---|---|
| None | 25 MB |
| Level 2 | 50 MB |
| Level 3 | 100 MB |
For enterprise use or files exceeding Discord's upload limit, the recommended pattern is:
- Upload to external storage — Amazon S3, Cloudflare R2, Google Drive, etc. using your own credentials.
- Generate a temporary link — e.g. an S3 presigned URL with a short TTL.
- Send the link back to Discord — post the URL as a regular message in the thread.
See the Enterprise / Large Files diagram above for the full flow.
- No file size limit — S3/R2 handles files of any size.
- Files stay off Discord — you control where data lives, important for compliance and data governance.
- You control the TTL — the link expires on your terms; no permanent file sitting in a Discord CDN.
import boto3
s3 = boto3.client("s3")
# Upload
s3.upload_file("/path/to/report.pdf", "my-bucket", "reports/report.pdf")
# Generate presigned URL (expires in 1 hour)
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": "my-bucket", "Key": "reports/report.pdf"},
ExpiresIn=3600,
)
# Then send `url` as a Discord message via the API| Use Case | Typical Format | Notes |
|---|---|---|
| Code patches | .diff, .patch |
Attach as file to avoid Discord's 2000-char limit |
| Logs | .log, .txt |
Truncate or compress large logs |
| Reports | .pdf, .csv, .html |
PDF renders a preview in Discord |
| Archives | .zip, .tar.gz |
Bundle multiple files |
- Never hardcode the bot token. Read from
$DISCORD_BOT_TOKENor a mounted secret. - Validate file paths. Sanitize any dynamically constructed paths to prevent path traversal.
- Check file size before uploading to avoid silent failures.
- Sensitive content. Do not send files containing secrets, credentials, or PII unless the user explicitly requests it.
- Rate limits. Discord enforces per-channel rate limits. Space uploads when sending multiple files.
Ensure your bot has these permissions in the Discord Developer Portal:
-
Send Messages -
Send Messages in Threads -
Attach Files
Q: Can OpenAB relay files natively? A: Not currently. OpenAB streams text via ACP JSON-RPC. File sending is done out-of-band by the agent.
Q: What if the file is too large? A: Use the Large Files & Enterprise Best Practice pattern — upload to S3/R2/Google Drive, generate a temporary link, and send the URL in the message.
Q: Does this work with Slack / Telegram / LINE?
A: Same concept — call the platform's file upload API using the channel ID from sender_context. API details differ per platform. For Slack, use files.upload. For Telegram, use sendDocument.