diff --git a/app/(main)/[locale]/page.tsx b/app/(main)/[locale]/page.tsx
index 7345bb80..a683d1c6 100644
--- a/app/(main)/[locale]/page.tsx
+++ b/app/(main)/[locale]/page.tsx
@@ -77,6 +77,7 @@ import { appLifecycleHooks, uiHooks, routerHooks, toastHooks, emailHooks } from
import { emailToReadView } from "@/lib/plugin-projection";
import { buildQuoteHeader } from "@/lib/quote-header";
import { buildReplySubject, buildForwardSubject } from "@/lib/subject-prefix";
+import { buildForwardAsAttachmentPayload } from "@/lib/forward-as-attachment";
import { getEffectiveLocale } from '@/i18n/detect-locale';
import type { QuoteHeader } from "@/lib/plugin-types";
@@ -1539,6 +1540,52 @@ export default function Home() {
if (isMobile) setActiveView('viewer');
};
+ // Forward the original message as a message/rfc822 attachment instead of
+ // inline-quoted text - e.g. for reporting spam to an upstream gateway
+ // that expects the raw original as an attachment, or preserving exact
+ // formatting/headers the recipient needs to see untouched. Reuses the
+ // same attachment-carry-forward mechanism native Forward already uses
+ // for a forwarded message's own attachments (see the `attachments`
+ // useState initializer in email-composer.tsx) - we just add one more
+ // synthetic entry representing the whole original message, referenced
+ // by its existing blobId (no re-fetch/re-upload needed - JMAP blobs are
+ // account-scoped, not per-email). Skips prepareComposerQuoteHeader
+ // entirely, so the body starts blank instead of quoting the original.
+ const handleForwardAsAttachment = async () => {
+ if (!selectedEmail) return;
+ const payload = buildForwardAsAttachmentPayload(selectedEmail, t('email_composer.prefix.forward'));
+ if (!payload) return;
+
+ const ok = await emailHooks.onBeforeForward.intercept({
+ originalEmailId: selectedEmail.id,
+ originalEmail: emailToReadView(selectedEmail),
+ mode: 'forward' as const,
+ });
+ if (!ok) return;
+
+ startFreshComposerSession();
+ setPendingDraft({
+ to: "",
+ cc: "",
+ bcc: "",
+ subject: payload.subject,
+ body: "",
+ showCc: false,
+ showBcc: false,
+ selectedIdentityId: null,
+ subAddressTag: "",
+ mode: "forward",
+ draftId: null,
+ replyTo: {
+ subject: selectedEmail.subject,
+ attachments: [payload.attachment],
+ },
+ });
+ setComposerMode('forward');
+ setShowComposer(true);
+ if (isMobile) setActiveView('viewer');
+ };
+
const handleDelete = async (emailToDelete: Email | null = selectedEmail) => {
if (!client || !emailToDelete) return;
@@ -3436,6 +3483,7 @@ export default function Home() {
onReply={handleReply}
onReplyAll={handleReplyAll}
onForward={handleForward}
+ onForwardAsAttachment={handleForwardAsAttachment}
onDelete={() => {
// Deleting the open message returns to the list (Gmail-style),
// not the next email — unless the user turned the setting off.
diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx
index dd59209e..b95e019e 100644
--- a/components/email/email-viewer.tsx
+++ b/components/email/email-viewer.tsx
@@ -19,6 +19,7 @@ import {
Reply,
ReplyAll,
Forward,
+ Paperclip,
Trash2,
Archive,
Star,
@@ -109,6 +110,7 @@ interface EmailViewerProps {
onReply?: (draftText?: string) => void;
onReplyAll?: () => void;
onForward?: () => void;
+ onForwardAsAttachment?: () => void;
onDelete?: () => void;
onArchive?: () => void;
onToggleStar?: () => void;
@@ -621,6 +623,7 @@ export function EmailViewer({
onReply,
onReplyAll,
onForward,
+ onForwardAsAttachment,
onDelete,
onArchive,
onToggleStar,
@@ -3340,6 +3343,16 @@ export function EmailViewer({
)}
+ {/* Forward as attachment */}
+ {onForwardAsAttachment && (
+
+ )}
{/* Export email */}
)}
+ {onForwardAsAttachment && (
+
+ )}