diff --git a/app/(main)/[locale]/layout.tsx b/app/(main)/[locale]/layout.tsx
index 60944ea5..8c3126af 100644
--- a/app/(main)/[locale]/layout.tsx
+++ b/app/(main)/[locale]/layout.tsx
@@ -9,6 +9,7 @@ import { ProtocolLaunchHandlerProvider } from "@/components/protocol/protocol-la
import { ProInterfaceRedirect } from "@/components/pro/pro-interface-redirect";
import { PluginDialogHost } from "@/components/plugins/plugin-dialog-host";
import { PluginConsentDialog } from "@/components/plugins/plugin-consent-dialog";
+import { PWAInstallPrompt } from "@/components/pwa-install-prompt";
import { locales } from "@/i18n/routing";
export default async function LocaleLayout({
@@ -41,6 +42,7 @@ export default async function LocaleLayout({
{children}
+
diff --git a/app/(main)/[locale]/page.tsx b/app/(main)/[locale]/page.tsx
index e951b6c9..63611cb4 100644
--- a/app/(main)/[locale]/page.tsx
+++ b/app/(main)/[locale]/page.tsx
@@ -55,7 +55,7 @@ import { useProMultiAccountMailboxes } from "@/hooks/use-pro-multi-account-mailb
import { Input } from "@/components/ui/input";
import { FilePreviewModal } from "@/components/files/file-preview-modal";
import { isFilePreviewable } from "@/lib/file-preview";
-import { appendPlainTextSignature } from "@/lib/signature-utils";
+import { appendHtmlSignature, appendPlainTextSignature } from "@/lib/signature-utils";
import { computeReplyThreadingHeaders } from "@/lib/email-threading";
import { EML_IMPORT_ACCEPT, expandImportableEmails } from "@/lib/eml-import";
import { resolveReplyFrom } from "@/lib/reply-identity";
@@ -72,6 +72,7 @@ import { plainTextToComposerBody } from "@/lib/email-composer-utils";
import { appLifecycleHooks, uiHooks, routerHooks, toastHooks, emailHooks } from "@/lib/plugin-hooks";
import { emailToReadView } from "@/lib/plugin-projection";
import { buildQuoteHeader } from "@/lib/quote-header";
+import { buildReplySubject, buildForwardSubject } from "@/lib/subject-prefix";
import { useLocaleStore } from "@/stores/locale-store";
import type { QuoteHeader } from "@/lib/plugin-types";
@@ -752,9 +753,9 @@ export default function Home() {
let title = t('email_composer.new_message');
if (baseSubject) {
if (effectiveMode === 'reply' || effectiveMode === 'replyAll') {
- title = baseSubject.startsWith('Re:') ? baseSubject : `Re: ${baseSubject}`;
+ title = buildReplySubject(baseSubject, t('email_composer.prefix.reply'));
} else if (effectiveMode === 'forward') {
- title = baseSubject.startsWith('Fwd:') ? baseSubject : `Fwd: ${baseSubject}`;
+ title = buildForwardSubject(baseSubject, t('email_composer.prefix.forward'));
} else {
title = baseSubject;
}
@@ -1130,6 +1131,7 @@ export default function Home() {
inReplyTo?: string[];
references?: string[];
delayedUntil?: string;
+ requestReadReceipt?: boolean;
}) => {
if (!client) return;
@@ -1137,7 +1139,7 @@ export default function Home() {
const effectiveMode = pendingDraft?.mode ?? composerMode;
const originalEmailId = selectedEmail?.id;
- const result = await sendEmail(client, data.to, data.subject, data.body, data.cc, data.bcc, data.identityId, data.fromEmail, data.draftId, data.fromName, data.htmlBody, data.attachments, data.inReplyTo, data.references, data.delayedUntil, data.envelopeMailFrom);
+ const result = await sendEmail(client, data.to, data.subject, data.body, data.cc, data.bcc, data.identityId, data.fromEmail, data.draftId, data.fromName, data.htmlBody, data.attachments, data.inReplyTo, data.references, data.delayedUntil, data.envelopeMailFrom, { requestReadReceipt: data.requestReadReceipt });
setShowComposer(false);
if (result.scheduled) {
await refreshScheduledMetadata(client);
@@ -2098,9 +2100,21 @@ export default function Home() {
// Append signature from the sending identity (fall back to primary
// when the reply-from lives on the same identity but a different alias).
- const finalBody = appendPlainTextSignature(body, sendingIdentity, {
- separator: useSettingsStore.getState().signatureSeparatorEnabled,
- });
+ const separator = useSettingsStore.getState().signatureSeparatorEnabled;
+ const finalBody = appendPlainTextSignature(body, sendingIdentity, { separator });
+
+ // When the identity has an HTML signature, send a matching HTML body so the
+ // signature keeps its formatting; appendPlainTextSignature would otherwise
+ // flatten it to plain text. Text-only identities keep the plain-text-only
+ // behavior (htmlBody stays undefined).
+ const escapedBody = body
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/\n/g, '
');
+ const finalHtmlBody = sendingIdentity?.htmlSignature?.trim()
+ ? appendHtmlSignature(`
${escapedBody}
`, sendingIdentity, { separator })
+ : undefined;
const originalEmailId = selectedEmail.id;
const sendDelaySeconds = useSettingsStore.getState().sendDelaySeconds;
@@ -2124,7 +2138,7 @@ export default function Home() {
const result = await sendEmail(
client,
[sender.email],
- `Re: ${selectedEmail.subject || "(no subject)"}`,
+ buildReplySubject(selectedEmail.subject || "(no subject)", t('email_composer.prefix.reply')),
finalBody,
undefined,
undefined,
@@ -2132,7 +2146,7 @@ export default function Home() {
headerFromEmail,
undefined,
headerFromName,
- undefined,
+ finalHtmlBody,
undefined,
threading?.inReplyTo,
threading?.references,
diff --git a/app/(main)/admin/_tabs/branding.tsx b/app/(main)/admin/_tabs/branding.tsx
index 09bd2ce7..bfa87c13 100644
--- a/app/(main)/admin/_tabs/branding.tsx
+++ b/app/(main)/admin/_tabs/branding.tsx
@@ -2,7 +2,7 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import { Save, Loader2, RotateCcw, ImageIcon, Upload, Trash2, Globe, Plus, X } from 'lucide-react';
-import { apiFetch } from '@/lib/browser-navigation';
+import { apiFetch, withBasePath } from '@/lib/browser-navigation';
import {
BRANDING_OVERRIDE_KEYS,
parseDomainBranding,
@@ -33,6 +33,8 @@ const TEXT_FIELDS = [
const PWA_IMAGE_FIELDS = [
{ key: 'pwaIconUrl', label: 'PWA Icon', accept: '.svg,.png,.jpg,.webp' },
+ { key: 'pwaScreenshotMobileUrl', label: 'PWA Screenshot (Mobile)', accept: '.png,.jpg,.webp' },
+ { key: 'pwaScreenshotDesktopUrl', label: 'PWA Screenshot (Desktop)', accept: '.png,.jpg,.webp' },
] as const;
const PWA_TEXT_FIELDS = [
@@ -528,7 +530,7 @@ export function BrandingTab() {
})
{ (e.target as HTMLImageElement).style.display = 'none'; }}
@@ -606,7 +608,7 @@ export function BrandingTab() {
})
{ (e.target as HTMLImageElement).style.display = 'none'; }}
diff --git a/app/(main)/layout.tsx b/app/(main)/layout.tsx
index ac5c3b42..99468f94 100644
--- a/app/(main)/layout.tsx
+++ b/app/(main)/layout.tsx
@@ -2,7 +2,6 @@ import type { Metadata, Viewport } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { headers } from "next/headers";
import { getLocale } from "next-intl/server";
-import { PWAInstallPrompt } from "@/components/pwa-install-prompt";
import { ServiceWorkerRegistration } from "@/components/service-worker-registration";
import { configManager } from "@/lib/admin/config-manager";
import { withBasePath } from "@/lib/browser-navigation";
@@ -92,7 +91,6 @@ export default async function RootLayout({
>
{children}
-