feat(attachments): R2 toggle, storage metrics, and demo R2 config

Add an ATTACHMENTS_ENABLED switch (default on when R2 is bound) via a
central getAttachmentBucket helper, surface R2 + estimated KV usage
against the free tier on the status page and /api/stats (refreshed by the
hourly cron), let setup.sh create and wire the R2 bucket, and bind the
demo bucket so the deployed demo has attachments.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
Julien HerrandClaude Opus 4.7 committed 2026-05-23 17:33:50 +02:00
1 parent 7226e718f7
commit f150d40c45
19 files changed
+387 -23

No files matched your search

+6 -4
View File
@@ -13,6 +13,7 @@ import {
extractEmailDomain,
} from "../utils/favicon-fetcher";
import { parseOneClickUnsubscribe } from "../utils/unsubscribe";
import { getAttachmentBucket } from "../utils/attachments";
import { logger } from "./logger";
import { FEED_MAX_BYTES } from "../config/constants";
@@ -170,9 +171,10 @@ export async function storeEmail(
env: Env,
ctx?: ExecutionContext,
): Promise<void> {
const attachmentBucket = getAttachmentBucket(env);
const storedAttachments: AttachmentData[] =
env.ATTACHMENT_BUCKET && input.attachments?.length
? await uploadAttachments(input.attachments, env.ATTACHMENT_BUCKET)
attachmentBucket && input.attachments?.length
? await uploadAttachments(input.attachments, attachmentBucket)
: [];
const emailData = {
@@ -249,10 +251,10 @@ export async function storeEmail(
}
const r2Deletions =
env.ATTACHMENT_BUCKET && toDelete.length > 0
attachmentBucket && toDelete.length > 0
? toDelete
.flatMap((e) => e.attachmentIds ?? [])
.map((id) => env.ATTACHMENT_BUCKET!.delete(id))
.map((id) => attachmentBucket.delete(id))
: [];
await Promise.all([