compose: single close button + attachment support (paste, drag-drop, file picker)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -26,20 +26,32 @@ async function getSmtpTransport(userId: number): Promise<{ transport: ReturnType
|
||||
return { transport, from: acct.email };
|
||||
}
|
||||
|
||||
// POST /send — compose/reply. Gmail auto-files the sent copy in "Sent", so IMAP sync picks it up.
|
||||
// POST /send — compose/reply (multipart; `files` are attachments). Gmail auto-files the sent copy in
|
||||
// "Sent", so IMAP sync picks it up.
|
||||
emailRouter.post('/send', async (ctx) => {
|
||||
const user = ctx.get('user');
|
||||
const b = await ctx.req.json<{ to: string; cc?: string; bcc?: string; subject?: string; body?: string; inReplyTo?: string }>();
|
||||
if (!b.to?.trim()) throw errors.BAD_REQUEST('At least one recipient is required');
|
||||
const form = await ctx.req.parseBody({ all: true });
|
||||
const str = (v: unknown) => (typeof v === 'string' ? v : '');
|
||||
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 attachments = await Promise.all(
|
||||
files.map(async (f) => ({ filename: f.name, content: Buffer.from(await f.arrayBuffer()), contentType: f.type || undefined })),
|
||||
);
|
||||
|
||||
const inReplyTo = str(form.inReplyTo).trim();
|
||||
const { transport, from } = await getSmtpTransport(user.id);
|
||||
await transport.sendMail({
|
||||
from,
|
||||
to: b.to,
|
||||
cc: b.cc?.trim() || undefined,
|
||||
bcc: b.bcc?.trim() || undefined,
|
||||
subject: b.subject?.trim() || '(no subject)',
|
||||
text: b.body ?? '',
|
||||
...(b.inReplyTo ? { headers: { 'In-Reply-To': b.inReplyTo, References: b.inReplyTo } } : {}),
|
||||
to,
|
||||
cc: str(form.cc).trim() || undefined,
|
||||
bcc: str(form.bcc).trim() || undefined,
|
||||
subject: str(form.subject).trim() || '(no subject)',
|
||||
text: str(form.body),
|
||||
...(attachments.length ? { attachments } : {}),
|
||||
...(inReplyTo ? { headers: { 'In-Reply-To': inReplyTo, References: inReplyTo } } : {}),
|
||||
});
|
||||
return ctx.json({ ok: true });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user