feat: store email attachments in R2 and expose as RSS enclosures

Attachments from incoming emails are uploaded to an optional Cloudflare R2
bucket and exposed as <enclosure> elements in RSS and <link rel="enclosure">
in Atom feeds, served at /files/{id}/{filename} with immutable caching.

R2 is opt-in: if ATTACHMENT_BUCKET is not bound the feature is a no-op.
Attachments are cleaned up from R2 on email/feed deletion and during
size-based feed trimming. Adds MockR2 to the test setup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Julien Herr
2026-05-21 09:09:37 +02:00
parent 3e28246c61
commit e93bbb8d3e
15 changed files with 615 additions and 19 deletions
+8
View File
@@ -42,6 +42,7 @@ function buildFeed(
for (const email of emails) {
const uniqueId = `${email.receivedAt}-${Buffer.from(email.subject).toString("base64").substring(0, 10)}`;
const firstAttachment = email.attachments?.[0];
feed.addItem({
title: email.subject,
id: uniqueId,
@@ -50,6 +51,13 @@ function buildFeed(
content: email.content,
author: [parseFromAddress(email.from)],
date: new Date(email.receivedAt),
enclosure: firstAttachment
? {
url: `${baseUrl}/files/${firstAttachment.id}/${encodeURIComponent(firstAttachment.filename)}`,
type: firstAttachment.contentType,
length: firstAttachment.size,
}
: undefined,
});
}