Work with attachments#

Upload and attach a file#

with open("agenda.pdf", "rb") as f:
    blob_id = client.upload_attachment(f.read(), content_type="application/pdf")

client.attach_to_event(
    event_id,
    blob_id=blob_id,
    name="Agenda",
    content_type="application/pdf",
)

upload_attachment is a raw HTTP upload, not a JMAP method call; it returns a blob ID to pass to attach_to_event or download_attachment().

attach_to_event reads the event’s current links, then replaces the whole property with the merged result. There’s no concurrency guard: a second concurrent call to this method for the same event can silently overwrite the first. Serialize calls for a given event if that matters to you.

List an event’s attachments#

attachments = client.get_event_attachments(event_id)
for attachment in attachments:
    print(attachment.title, attachment.href)

Download an attachment#

data = client.download_attachment(blob_id, content_type="application/pdf", filename="agenda.pdf")

content_type and filename only affect the response’s own headers; an omitted or wrong value doesn’t affect whether the download succeeds.

Cyrus limitation#

Cyrus accepts an attachment write but doesn’t reliably persist the attachment’s rel: "enclosure" marker, so get_event_attachments() may not find an attachment attach_to_event just wrote. Stalwart is unaffected.

Last updated on 2026-10-08.