Plane docs
Guides

Attachments and idempotency keys

Sending a file, and sending a message exactly once.

Attachments

Upload the bytes, then send the reference. References rather than bytes, because an event row is JSON in Postgres and a large one would not fit.

const ref = await client.blobs.upload({
  filename: "receipt.pdf",
  contentType: "application/pdf",
  data: base64,                    // 8 MiB cap on the decoded bytes
  sessionId                        // optional: scopes it to one session
})

await client.sessions.send({
  id: sessionId,
  message: "here is the receipt — does this match order A-1001?",
  attachments: [ref]
})

Images and text-shaped documents (PDF, plain text, CSV, JSON, Markdown) are accepted. An image or a PDF is rendered to the model as a native file part; a text document is rendered inline.

A type no provider will read is refused at upload, rather than best-efforted. It would otherwise reach the model as the sentence "there is a file", which looks like it worked and did not.

A reference the turn cannot read renders as a visible marker, for the same reason: a rendering that silently drops a file the user attached is worse than one that says so.

Idempotency keys

await client.sessions.send({ id, message, idempotencyKey: requestId })

A duplicate returns the original event and runs no second turn. The check and the write are one atomic decision under the lock that already serialises a session's writes, so the loser of a race blocks until the winner commits and then finds its row — rather than reading, finding nothing, and inserting a second message.

The key is unique per session, which is the grain the log is already keyed and locked by.

Use it wherever a retry is possible and a second turn would be wrong: a webhook handler, a queue consumer, a mobile client with an unreliable connection.

On this page