Attachments¶
Attachments let you include files with action dispatches. Email providers send them as MIME attachments, Slack and Discord upload them as files, and webhook providers include them as multipart form parts. Providers that don't support attachments simply ignore them.
Attachment Model¶
Each attachment carries five fields:
| Field | Type | Description |
|---|---|---|
id | string | User-defined identifier for referencing this attachment in chains and templates |
name | string | Human-readable display name |
filename | string | Filename with extension (e.g. "report.pdf") |
content_type | string | MIME type (e.g. "application/pdf") |
data_base64 | string | Base64-encoded file content |
The id field is set by you, not auto-generated. Use it to reference specific attachments across chain steps, sub-chains, and template profiles.
Dispatching with Attachments¶
Include attachments in the attachments array of a dispatch request:
curl -X POST http://localhost:8080/v1/dispatch \
-H "Content-Type: application/json" \
-d '{
"namespace": "notifications",
"tenant": "tenant-1",
"provider": "email",
"action_type": "send_email",
"payload": {
"to": "user@example.com",
"subject": "Monthly Report",
"body": "Please find the report attached."
},
"attachments": [
{
"id": "monthly-report",
"name": "Monthly Report",
"filename": "report-2026-02.pdf",
"content_type": "application/pdf",
"data_base64": "JVBERi0xLjQK..."
},
{
"id": "summary-csv",
"name": "Summary Data",
"filename": "summary.csv",
"content_type": "text/csv",
"data_base64": "bmFtZSxhbW91bnQK..."
}
]
}'
Provider Behavior¶
Each provider handles resolved attachments differently:
Email (SMTP / SES)¶
Attachments are sent as RFC 2045 MIME parts inside a multipart/mixed envelope. The filename and content_type map directly to the MIME part headers. This is how standard email clients display file attachments.
Slack¶
Attachments are uploaded as files using Slack's file upload API. Each attachment appears as a downloadable file in the channel.
Discord¶
Attachments are sent as file uploads in the Discord message. Each attachment appears as a downloadable file in the channel.
Webhook¶
Attachments are included as multipart/form-data parts alongside the JSON payload. Each part uses the filename as the form field name.
Other Providers¶
Providers that return false from supports_attachments() ignore attachments silently. The action is dispatched normally with just the payload.
Resource Limits¶
The gateway enforces two limits on attachments:
| Limit | Default | Description |
|---|---|---|
max_attachments_per_action | 10 | Maximum number of attachments per dispatch |
max_inline_bytes | 10 MB | Maximum decoded size per attachment |
These are configured in the server's [attachments] section:
The gateway validates both limits after decoding the base64 content. Actions that exceed either limit are rejected with an ATTACHMENT_ERROR outcome.
Template Integration¶
Attachment metadata is available inside payload templates via two context variables:
attachments -- List¶
An ordered list of attachment metadata objects (without data_base64):
Each object has four fields: id, name, filename, content_type.
attachments_by_id -- Map¶
A map from attachment id to the same metadata, for direct lookup:
Conditional rendering¶
{% if attachments %}
{{ attachments | length }} file(s) attached:
{% for att in attachments %}
- {{ att.name }} ({{ att.filename }})
{% endfor %}
{% else %}
No files attached.
{% endif %}
Binary content (data_base64) is intentionally excluded from the template context to prevent multi-megabyte strings inside the rendering engine.
Audit Trail¶
Attachment metadata (not binary data) is recorded in the audit trail for every dispatched action. Each audit record includes an attachment_metadata array:
{
"attachment_metadata": [
{
"id": "monthly-report",
"name": "Monthly Report",
"filename": "report-2026-02.pdf",
"content_type": "application/pdf",
"size_bytes": 142857
}
]
}
The size_bytes field reflects the decoded binary size (the actual file size), not the base64-encoded string length.
Client SDK Usage¶
Rust¶
use acteon_core::{Action, Attachment};
let action = Action::new("notifications", "tenant-1", "email", "send_email",
serde_json::json!({"to": "user@example.com", "subject": "Report"}),
)
.with_attachments(vec![
Attachment {
id: "report".into(),
name: "Monthly Report".into(),
filename: "report.pdf".into(),
content_type: "application/pdf".into(),
data_base64: base64_encoded_data,
},
]);
Python¶
from acteon_client import ActeonClient, Attachment
client = ActeonClient("http://localhost:8080")
client.dispatch(
namespace="notifications",
tenant="tenant-1",
provider="email",
action_type="send_email",
payload={"to": "user@example.com", "subject": "Report"},
attachments=[
Attachment(
id="report",
name="Monthly Report",
filename="report.pdf",
content_type="application/pdf",
data_base64=base64_encoded_data,
),
],
)
Node.js / TypeScript¶
import { ActeonClient, Attachment } from "acteon-client";
const client = new ActeonClient("http://localhost:8080");
await client.dispatch({
namespace: "notifications",
tenant: "tenant-1",
provider: "email",
actionType: "send_email",
payload: { to: "user@example.com", subject: "Report" },
attachments: [
{
id: "report",
name: "Monthly Report",
filename: "report.pdf",
content_type: "application/pdf",
data_base64: base64EncodedData,
},
],
});
Go¶
client := acteon.NewClient("http://localhost:8080")
client.Dispatch(ctx, acteon.DispatchRequest{
Namespace: "notifications",
Tenant: "tenant-1",
Provider: "email",
ActionType: "send_email",
Payload: map[string]any{"to": "user@example.com", "subject": "Report"},
Attachments: []acteon.Attachment{
acteon.NewAttachment("report", "Monthly Report", "report.pdf",
"application/pdf", base64EncodedData),
},
})
Java¶
ActeonClient client = new ActeonClient("http://localhost:8080");
client.dispatch(DispatchRequest.builder()
.namespace("notifications")
.tenant("tenant-1")
.provider("email")
.actionType("send_email")
.payload(Map.of("to", "user@example.com", "subject", "Report"))
.attachments(List.of(
new Attachment("report", "Monthly Report", "report.pdf",
"application/pdf", base64EncodedData)
))
.build());