feat: add /api/translate proxy and expose email body to plugins

This commit is contained in:
Linus Rath
2026-05-31 18:01:00 +02:00
parent 6ee3849463
commit bc322a1e69
3 changed files with 337 additions and 0 deletions
+35
View File
@@ -5,6 +5,40 @@
import type { Email } from '@/lib/jmap/types';
import type { EmailReadView } from '@/lib/plugin-types';
// Resolve a message's plain-text body from its JMAP body parts. Plugins that
// translate or scan content need the real body, not just the short `preview`
// snippet. Prefers text/plain parts; falls back to stripped HTML, then preview.
function plainTextFromEmail(email: Email): string {
const values = email.bodyValues || {};
const collect = (parts?: { partId: string }[]) =>
(parts || [])
.map((p) => values[p.partId]?.value)
.filter((v): v is string => typeof v === 'string' && v.length > 0)
.join('\n')
.trim();
const text = collect(email.textBody);
if (text) return text;
const html = collect(email.htmlBody);
if (html) {
return html
.replace(/<(script|style)[\s\S]*?<\/\1>/gi, ' ')
.replace(/<[^>]+>/g, ' ')
.replace(/&nbsp;/gi, ' ')
.replace(/&amp;/gi, '&')
.replace(/&lt;/gi, '<')
.replace(/&gt;/gi, '>')
.replace(/&#39;|&apos;/gi, "'")
.replace(/&quot;/gi, '"')
.replace(/[ \t]{2,}/g, ' ')
.replace(/\n{3,}/g, '\n\n')
.trim();
}
return (email.preview || '').trim();
}
export function emailToReadView(email: Email): EmailReadView {
return {
id: email.id,
@@ -19,6 +53,7 @@ export function emailToReadView(email: Email): EmailReadView {
isFlagged: !!email.keywords?.['$flagged'],
hasAttachment: email.hasAttachment,
preview: email.preview || '',
text: plainTextFromEmail(email),
keywords: Object.keys(email.keywords || {}).filter(k => email.keywords[k]),
auth: email.authenticationResults,
};
+4
View File
@@ -374,6 +374,10 @@ export interface EmailReadView {
hasAttachment: boolean;
preview: string;
keywords: string[];
/** Full plain-text body of the message (HTML-only bodies are stripped to
* text). Empty string when the host hasn't loaded the body. Same
* `email:read` sensitivity as the rest of this view. */
text: string;
/**
* Parsed Authentication-Results header (SPF, DKIM, DMARC, reverse-DNS).
* Absent on stores that didn't parse the header (e.g. bodies not yet