compose: bcc field, inline pasted/dropped images, attach button stays regular

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 07:52:11 +00:00
co-authored by Claude Opus 4.8
parent ced1796a56
commit c590d6253c
2 changed files with 102 additions and 20 deletions
+16 -4
View File
@@ -35,12 +35,23 @@ emailRouter.post('/send', async (ctx) => {
const to = str(form.to).trim();
if (!to) throw errors.BAD_REQUEST('At least one recipient is required');
const rawFiles = form.files;
const files = (Array.isArray(rawFiles) ? rawFiles : rawFiles ? [rawFiles] : []).filter((f): f is File => f instanceof File);
const toFiles = (raw: unknown) => (Array.isArray(raw) ? raw : raw ? [raw] : []).filter((f): f is File => f instanceof File);
const attachments = await Promise.all(
files.map(async (f) => ({ filename: f.name, content: Buffer.from(await f.arrayBuffer()), contentType: f.type || undefined })),
toFiles(form.files).map(async (f) => ({ filename: f.name, content: Buffer.from(await f.arrayBuffer()), contentType: f.type || undefined })),
);
// Inline images: cid `inline-<i>` matches the `<img src="cid:inline-i">` the composer put in the html.
const inline = await Promise.all(
toFiles(form.inline).map(async (f, i) => ({
filename: f.name,
content: Buffer.from(await f.arrayBuffer()),
contentType: f.type || undefined,
cid: `inline-${i}`,
contentDisposition: 'inline' as const,
})),
);
const allAttachments = [...attachments, ...inline];
const html = str(form.html).trim();
const inReplyTo = str(form.inReplyTo).trim();
const { transport, from } = await getSmtpTransport(user.id);
await transport.sendMail({
@@ -50,7 +61,8 @@ emailRouter.post('/send', async (ctx) => {
bcc: str(form.bcc).trim() || undefined,
subject: str(form.subject).trim() || '(no subject)',
text: str(form.body),
...(attachments.length ? { attachments } : {}),
...(html ? { html } : {}),
...(allAttachments.length ? { attachments: allAttachments } : {}),
...(inReplyTo ? { headers: { 'In-Reply-To': inReplyTo, References: inReplyTo } } : {}),
});
return ctx.json({ ok: true });