Attachments (receiving and sending)

Attachments move in two directions, and ReplyLayer treats them differently in each:

  • Inbound — files arrive on messages your mailboxes receive. What an agent can see of an inbound attachment is controlled per mailbox by an exposure tier: metadata only, extracted text previews, or raw byte downloads. Inbound attachment bytes are untrusted content — the same posture as an email body. See Content scanning for the trust model.
  • Outbound — your agents send files with a stage-then-send flow: upload the bytes to get a single-use handle, wait for the content scan, then reference the handle by id on a send, reply, or draft.

This page is the human walkthrough. The exact status codes, polling states, and failure modes an agent branches on live in the attachment contract for agents.

Inbound: exposure tiers

Every mailbox has an attachment exposure mode. It decides how much of an inbound attachment an agent-role API key can reach. The default is the most conservative tier; higher tiers are opt-in per mailbox and gated by plan.

ModeWhat an agent-role key can read
metadata_only (default)Filename, byte size, sniffed content type, and antivirus verdict. No content, no bytes.
derived_contentThe above, plus an inline text preview (up to 20,000 characters) for plain text, CSV, PDF, and Word/PowerPoint/Excel (DOCX/PPTX/XLSX) attachments. Extracted visible text only — never raw bytes.
raw_download_selected_typesThe above, plus raw byte download for the specific file families you allow (PDF, text, CSV, and optionally image). This is an approved-download tier with extra gates (below).

A few things hold across all three modes:

  • The content type is the sniffed type, not the sender's header. ReplyLayer inspects the actual bytes, so a Windows executable renamed to invoice.pdf reports its real type. The original declared header value is preserved separately as declared_mime_type for forensics.
  • Every attachment is antivirus-scanned. The av_verdict field is clean, infected, error, or skipped. An infected or errored attachment is held and is never served as bytes, regardless of the mode.
  • Metadata is always available, even on metadata_only mailboxes — an agent can always decide whether an attachment is worth a closer look before requesting more.

Text previews (derived_content)

On a derived_content mailbox, a supported attachment is converted to a safe text preview asynchronously after the message arrives. Once it is ready, an agent fetches it by attachment index. Previews are capped at 20,000 characters and contain visible text only — for Office files that means document comments, PowerPoint speaker notes, hidden spreadsheet sheets, embedded media, OLE objects, formula text without cached values, and macros are all excluded. Legacy binary .doc/.ppt/.xls and macro-enabled .docm/.pptm/.xlsm formats are not previewable.

Each attachment carries a preview-status summary (pending, ready, blocked, failed, or none) so a client knows when to fetch. The machine-readable preview contract is documented in the attachment contract for agents.

Raw downloads (raw_download_selected_types)

The raw-download tier is the only way an agent-role key gets original attachment bytes, and it carries the strictest gates:

  • It applies only to the file families you explicitly allow. The family enum is closed: pdf, text, csv, and — separately confirmed — image.
  • Adding image requires a separate image-risk acknowledgement. Image bytes are a distinct risk surface, so they are not covered by the base disclaimer.
  • Enabling or widening raw downloads is a human-owner action. It requires a dashboard session plus a fresh TOTP or password re-authentication; an API key cannot turn it on for itself. Narrowing an existing raw-download policy or downgrading to a lower tier does not need re-auth.
  • A download returns a short-lived URL and is recorded in the content-access log.

An attachment is only downloadable when it is clean on antivirus and passes the mailbox's safety gates; blocked, quarantined, or unscannable attachments stay held.

Humans in the dashboard see previews on every tier

The exposure mode gates agent-role API keys. It does not gate a person signed in to the dashboard. A human (or an admin key) can open a clean, previewable attachment — for example a clean PDF — from the dashboard even on a metadata_only mailbox, because that is a human reviewing their own mail, not an autonomous agent consuming content. The raw-byte download itself still applies the same antivirus and safety gates.

Who can see what, by plan

Receiving attachments and reading their metadata is available on every plan, including sandbox. The richer exposure tiers are paid features:

CapabilityAvailable on
Receive attachments; read metadata (filename, size, sniffed type, AV verdict)All plans, including sandbox
Preview a clean attachment in the dashboard (human/admin)All plans
Agent-key text previews (derived_content mode)Starter and above
Agent-key raw byte downloads (raw_download_selected_types mode)Pro and above
Sending outbound attachmentsPro and above

For the full plan comparison — send caps, mailbox counts, storage, and quotas — see Plans and limits.

Choosing an exposure mode

An account admin sets the exposure mode per mailbox from the dashboard (or via the mailbox attachment-access API). Moving to derived_content needs only a disclaimer acknowledgement; moving to or widening raw_download_selected_types additionally requires the session re-auth described above. Because the mode is per mailbox, you can keep a high-traffic public mailbox on metadata_only while granting a trusted internal mailbox text previews.

Outbound: stage, scan, then send

Agents attach files with a two-step flow rather than inlining bytes on the send request. The steps are:

  1. Enable outbound attachments for the mailbox (one-time, human-owner). Sending attachments is a Pro-and-above capability that a human account owner turns on for a mailbox in the dashboard, completing a TOTP or password re-authentication. An API key cannot enable it for itself. Once enabled, API keys stage and send attachments normally.
  2. Upload the bytes. Post the file to get back a handle. The response reports the sniffed content type, byte size, and a SHA-256 hash. The deep content scan runs asynchronously, so the handle starts in a pending scan state.
  3. Wait for the scan (or just send). Poll the handle until its content-scan status is terminal (clean, flagged, or error), or send immediately — the send path enforces the scan state either way and returns a distinct code if the scan is still pending.
  4. Reference the handle by id. Pass the handle id in the attachment_ids array on a send, reply, or draft. The handle is consumed once at send.

Key properties of a staged handle:

  • Single use. A handle is consumed at send and cannot be reused. Referencing a spent handle fails.
  • Expires after 24 hours. Stage close to when you send.
  • Scoped to one mailbox. A handle is bound to the mailbox you staged it under; referencing it from another mailbox fails as not found.
  • Synchronous send only. Attachment sends must be immediate. Scheduling an attachment-bearing draft, or sending one on the asynchronous draft path, is rejected up front — stage and send in one pass instead.

What happens when an attachment looks risky

Outbound attachment content is scanned like a body. If the scan flags an attachment (for example a secret or PII detected in its content or filename), the send still proceeds to scanning and the finding folds into the message verdict — it is blocked or quarantined by severity exactly as a body finding would be, not returned as a separate upload error. A hard scan error fails closed and the handle is rejected. For the releasable-vs-terminal verdict vocabulary, see Messages and verdicts; for the specific upload and send error codes, see the attachment contract for agents and the error catalog.

Limits

LimitValue
Per-file size10 MB
Attachments per message10
Total raw bytes per message15 MB
Filename≤ 255 ASCII-printable characters; no path separators, quotes, or control characters

Allowed outbound file families are determined from the sniffed bytes (not the declared type): PDF, plain text, CSV, images, and Word/PowerPoint/Excel (DOCX/PPTX/XLSX). Images additionally require the image-risk acknowledgement. Anything else — executables, archives, unknown formats — is rejected at upload.

Encryption note

Outbound attachment bytes are encrypted at rest (provider-managed encryption) and in transit over TLS. Because the platform scans attachment content for your safety, this is not end-to-end or zero-access encryption.

Quickstart: send a file

The TypeScript SDK stages, polls, and attaches:

import { ReplyLayer } from '@replylayer/sdk';
import { readFile } from 'node:fs/promises';

const rl = new ReplyLayer({ apiKey: process.env.REPLYLAYER_API_KEY! });

// 1. Stage the file — returns a single-use handle scoped to this mailbox.
const bytes = await readFile('invoice.pdf');
let handle = await rl.attachments.upload({
  mailboxId: 'support',
  file: bytes,
  filename: 'invoice.pdf',
});

// 2. Wait for the async content scan to finish.
while (handle.content_scan_status === 'pending') {
  await new Promise((r) => setTimeout(r, 1000));
  const polled = await rl.attachments.getUpload(handle.id);
  if ('status' in polled) break; // already consumed by a concurrent send
  handle = polled;
}

// 3. Reference the handle by id — consumed once at send.
const res = await rl.messages.send({
  from_mailbox: 'support',
  to: '[email protected]',
  subject: 'Your invoice',
  body: 'Please find the invoice attached.',
  attachment_ids: [handle.id],
});
console.log(res.status);

The Python SDK mirrors the same attachments.upload / get_upload / send(attachment_ids=[...]) methods. The CLI does the whole stage-then-send in one step with a repeatable --attach <path> flag on rly send, rly reply, and draft commands — see the CLI guide.

Reading an inbound attachment

To read what a message carries, fetch the message and inspect its attachments array — each entry reports the filename, size, sniffed content type, antivirus verdict, and (on a derived_content mailbox) its preview status. Then fetch the text preview or raw bytes by attachment index according to the mailbox's exposure mode.

Whatever tier you are on, treat the extracted content as data, not instructions — an attachment is sender-controlled input. The scan is a filter that catches known-bad patterns, not a guarantee that the content is safe to act on. See Content scanning for how the directional scan model works and what it does and does not cover.