feat: choose exact-address or same-domain matching for reply from received address #1000

This commit is contained in:
Linus Rath
2026-09-09 19:27:08 +02:00
parent c395226926
commit 565d5c1095
35 changed files with 238 additions and 5 deletions
+1
View File
@@ -52,6 +52,7 @@ const RESTRICTABLE_SETTINGS = [
{ key: 'sendConfirmation', label: 'Send Confirmation', category: 'Composer', type: 'boolean' },
{ key: 'defaultReplyMode', label: 'Default Reply Mode', category: 'Composer', type: 'enum', allowedValues: ['reply', 'reply-all'] },
{ key: 'autoSelectReplyIdentity', label: 'Auto-select Reply Identity', category: 'Composer', type: 'boolean' },
{ key: 'replyIdentityMatch', label: 'Reply Identity Matching', category: 'Composer', type: 'enum', allowedValues: ['exact', 'domain'] },
{ key: 'plainTextMode', label: 'Plain Text Only', category: 'Composer', type: 'boolean' },
{ key: 'sessionTimeout', label: 'Session Timeout', category: 'Privacy', type: 'number' },
{ key: 'emailNotificationsEnabled', label: 'Email Notifications', category: 'Notifications', type: 'boolean' },
@@ -102,6 +102,7 @@ vi.mock('@/stores/settings-store', () => {
plainTextMode: false,
subAddressDelimiter: '+',
autoSelectReplyIdentity: false,
replyIdentityMatch: 'domain',
attachmentReminderEnabled: false,
attachmentReminderKeywords: [],
sendDelaySeconds: 0,
@@ -323,6 +324,21 @@ describe('composer reply addressing', () => {
}
});
// #1000: on a domain whose other addresses are distribution lists rather
// than catch-all aliases, the setting can stay on but limited to configured
// identities, so a reply to list@ gets no From override.
it('does not rewrite From when matching is limited to exact addresses', () => {
const settings = useSettingsStore as unknown as { setState: (p: Record<string, unknown>) => void };
settings.setState({ autoSelectReplyIdentity: true, replyIdentityMatch: 'exact' });
try {
render(<EmailComposer mode="reply" replyTo={RECEIVED_CATCH_ALL} />);
expect(screen.queryByDisplayValue('colleague@example.com')).toBeNull();
expect(identitySelect().value).toBe('id-me');
} finally {
settings.setState({ autoSelectReplyIdentity: false, replyIdentityMatch: 'domain' });
}
});
it('never rewrites From on a forward, even with the setting on', () => {
setAutoSelect(true);
try {
+6 -2
View File
@@ -312,6 +312,7 @@ export function EmailComposer({
const plainTextMode = useSettingsStore((state) => state.plainTextMode);
const subAddressDelimiter = useSettingsStore((state) => state.subAddressDelimiter);
const autoSelectReplyIdentity = useSettingsStore((state) => state.autoSelectReplyIdentity);
const replyIdentityMatch = useSettingsStore((state) => state.replyIdentityMatch);
const attachmentReminderEnabled = useSettingsStore((state) => state.attachmentReminderEnabled);
const attachmentReminderKeywords = useSettingsStore((state) => state.attachmentReminderKeywords);
const emptySubjectWarningEnabled = useSettingsStore((state) => state.emptySubjectWarningEnabled);
@@ -878,9 +879,11 @@ export function EmailComposer({
// Catch-all From rewrite: opt-in, and never on a forward. A reply continues
// a thread whose participants already know the addressing; a forward
// introduces the rewritten From to a recipient the user just typed, who has
// no way to tell it is not really from that person.
// no way to tell it is not really from that person. `replyIdentityMatch`
// lets a user keep the setting on but limit it to configured identities,
// for domains where the other addresses are distribution lists (#1000).
if (autoSelectReplyIdentity && mode !== 'forward') {
const resolved = resolveReplyFrom(identities, recipients);
const resolved = resolveReplyFrom(identities, recipients, replyIdentityMatch);
if (resolved) {
setSelectedIdentityId(resolved.identityId);
if (resolved.overrideEmail && !fromOverrideEnabled) {
@@ -907,6 +910,7 @@ export function EmailComposer({
}
}, [
autoSelectReplyIdentity,
replyIdentityMatch,
composeFromAccountEmail,
fromOverrideEnabled,
identities,
+15 -1
View File
@@ -3,7 +3,7 @@
import { useState } from 'react';
import { useTranslations } from 'next-intl';
import { useSettingsStore } from '@/stores/settings-store';
import type { SendDelaySeconds } from '@/stores/settings-store';
import type { ReplyIdentityMatch, SendDelaySeconds } from '@/stores/settings-store';
import { useAuthStore } from '@/stores/auth-store';
import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section';
import { X } from 'lucide-react';
@@ -22,6 +22,7 @@ export function ComposingSettings() {
const {
autoSelectReplyIdentity,
replyIdentityMatch,
plainTextMode,
rtlEditingSupport,
attachmentReminderEnabled,
@@ -47,6 +48,19 @@ export function ComposingSettings() {
/>
</SettingItem>
{autoSelectReplyIdentity && (
<SettingItem label={t('reply_identity_match.label')} description={t('reply_identity_match.description')}>
<Select
value={replyIdentityMatch}
onChange={(value) => updateSetting('replyIdentityMatch', value as ReplyIdentityMatch)}
options={[
{ value: 'exact', label: t('reply_identity_match.exact') },
{ value: 'domain', label: t('reply_identity_match.domain') },
]}
/>
</SettingItem>
)}
<SettingItem label={t('plain_text_mode.label')} description={t('plain_text_mode.description')}>
<ToggleSwitch
checked={plainTextMode}
+15
View File
@@ -189,6 +189,21 @@ describe('resolveReplyFrom', () => {
expect(result).toEqual({ identityId: 'primary' });
});
// #1000: a domain whose extra addresses are distribution lists, not
// catch-all aliases. Exact mode keeps the identity matching but never
// surfaces a From override.
it('returns null instead of a catch-all override in exact mode', () => {
expect(resolveReplyFrom(identities, { to: [{ email: 'stripe@primary.com', name: 'Stripe' }] }, 'exact'))
.toBeNull();
});
it('still matches configured identities (exact and +tag) in exact mode', () => {
expect(resolveReplyFrom(identities, { to: [{ email: 'harry@secondary.com' }] }, 'exact'))
.toEqual({ identityId: 'secondary' });
expect(resolveReplyFrom(identities, { to: [{ email: 'harry+news@primary.com' }] }, 'exact'))
.toEqual({ identityId: 'primary' });
});
it('returns null when recipients are on foreign domains', () => {
expect(resolveReplyFrom(identities, { to: [{ email: 'nobody@elsewhere.com' }] }))
.toBeNull();
+17 -1
View File
@@ -198,6 +198,16 @@ export function findDraftIdentityId(
return base?.id ?? null;
}
/**
* How far `resolveReplyFrom` goes when matching received addresses:
* `exact` stops at the user's configured identities (steps 1-2 below),
* `domain` also takes the same-domain catch-all step (3), which rewrites
* `From:` to an address the user has not configured. Deployments where the
* extra addresses on a domain are distribution lists rather than aliases
* want `exact` (#1000).
*/
export type ReplyIdentityMatchMode = 'exact' | 'domain';
export interface ReplyFromResolution {
/** Identity to use for JMAP `identityId` and the SMTP envelope MAIL FROM. */
identityId: string;
@@ -223,12 +233,14 @@ export interface ReplyFromResolution {
* sub-addressing, reply as that identity with no override.
* 3. Else if a recipient address is on a domain that one of the identities
* uses, treat that recipient as a catch-all alias: return the matching
* identity + the recipient as a header-From override.
* identity + the recipient as a header-From override. Skipped in
* `exact` match mode.
* 4. Else return `null` (caller falls back to primary identity).
*/
export function resolveReplyFrom(
identities: Identity[],
recipients?: ReplyRecipients,
matchMode: ReplyIdentityMatchMode = 'domain',
): ReplyFromResolution | null {
if (identities.length === 0 || !recipients) {
return null;
@@ -265,6 +277,10 @@ export function resolveReplyFrom(
return { identityId: baseIdentity.id };
}
if (matchMode !== 'domain') {
return null;
}
const ownedDomains = new Set(identities.map((i) => domainOf(i.email)).filter(Boolean));
const catchAll = received.find((r) => {
+1
View File
@@ -97,6 +97,7 @@ export const tabSearchPaths: Record<Tab, string[]> = {
composing: [
'settings.email_behavior.attachment_reminder',
'settings.email_behavior.auto_select_reply_identity',
'settings.email_behavior.reply_identity_match',
'settings.email_behavior.plain_text_mode',
'settings.email_behavior.rtl_editing',
'settings.email_behavior.default_mail_program',
+6
View File
@@ -1335,6 +1335,12 @@
"label": "الرد من العنوان المستلَم إليه",
"description": "عند الرد، الإرسال من العنوان الذي أُرسلت إليه الرسالة أصلًا. يطابق الهويات أولًا؛ وبالنسبة لعمليات التسليم الشاملة للنطاق، يعيد كتابة ترويسة \"من\" إلى العنوان البديل مع الإرسال عبر هويتك الأساسية."
},
"reply_identity_match": {
"label": "مطابقة عنوان الاستلام",
"description": "أي عناوين الاستلام تُعدّ عناوينك. «العنوان المطابق فقط» يختار إحدى هوياتك المضبوطة فقط؛ أما «نفس النطاق» فيعامل أيضًا أي عنوان آخر على نطاقات هوياتك كاسم مستعار شامل ويستبدل به ترويسة المرسل. اختر العنوان المطابق إذا كانت تلك العناوين قوائم توزيع.",
"exact": "العنوان المطابق فقط",
"domain": "أي عنوان على نطاقاتي"
},
"signature_position": {
"label": "موضع التوقيع",
"description": "أين يُدرج توقيعك في الردود وإعادة التوجيه. أعلى النص المقتبس يُقرأ بشكل طبيعي كخاتمة للرد؛ وأسفله يُبقي الرسالة الأصلية متصلة.",
+6
View File
@@ -1335,6 +1335,12 @@
"label": "Respon des de l'adreça de recepció",
"description": "En respondre, envia des de l'adreça a la qual es va enviar originalment el missatge. Primer intenta coincidir amb les identitats; per als lliuraments de captura general de domini, reescriu la capçalera «De» amb l'àlies mentre envia a través de la identitat principal."
},
"reply_identity_match": {
"label": "Coincidència de l'adreça de recepció",
"description": "Quines adreces de recepció compten com a teves. «Només l'adreça exacta» tria únicament una de les teves identitats configurades; «mateix domini» també tracta qualsevol altra adreça dels teus dominis d'identitat com un àlies catch-all i hi reescriu la capçalera De. Tria l'adreça exacta si aquestes adreces són llistes de distribució.",
"exact": "Només l'adreça exacta",
"domain": "Qualsevol adreça dels meus dominis"
},
"signature_position": {
"label": "Posició de la signatura",
"description": "On inserir la signatura a les respostes i reenviaments. Abans del text citat es llegeix de manera natural com a tancament de la resposta; després manté el missatge original contigu.",
+6
View File
@@ -1332,6 +1332,12 @@
"label": "Automaticky vybírat adresu pro odpověď",
"description": "Při odpovídání automaticky přepnout adresu odesílatele na identitu, která původně obdržela zprávu"
},
"reply_identity_match": {
"label": "Porovnávání přijímací adresy",
"description": "Které přijímací adresy se počítají jako vaše. „Pouze přesná adresa“ vybere jen jednu z vašich nastavených identit; „stejná doména“ navíc považuje jakoukoli jinou adresu na doménách vašich identit za catch-all alias a přepíše na ni hlavičku Od. Pokud jsou tyto adresy distribuční seznamy, zvolte přesnou adresu.",
"exact": "Pouze přesná adresa",
"domain": "Jakákoli adresa na mých doménách"
},
"signature_position": {
"label": "Pozice podpisu",
"description": "Kam vložit podpis v odpovědích a přeposláních. Nad citovaným textem působí přirozeně jako zakončení odpovědi; pod ním zachovává původní zprávu vcelku.",
+6
View File
@@ -1335,6 +1335,12 @@
"label": "Svar fra modtaget adresse",
"description": "Når du svarer, send fra den adresse som beskeden oprindeligt blev sendt til. Matcher først identiteter; for domæne catch-all-leveringer omskriver Fra-headeren til aliaset, mens der sendes gennem din primære identitet."
},
"reply_identity_match": {
"label": "Matchning af modtageradresse",
"description": "Hvilke modtageradresser der tæller som dine. „Kun præcis adresse“ vælger udelukkende en af dine opsatte identiteter; „samme domæne“ behandler desuden enhver anden adresse på dine identitetsdomæner som et catch-all-alias og omskriver Fra-headeren til den. Vælg præcis adresse, hvis disse adresser er distributionslister.",
"exact": "Kun præcis adresse",
"domain": "Enhver adresse på mine domæner"
},
"signature_position": {
"label": "Signaturplacering",
"description": "Hvor din signatur indsættes i svar og videresendelser. Før citeret tekst læses naturligt som en afslutning på svaret; under bevarer den oprindelige besked sammenhængende.",
+6
View File
@@ -1332,6 +1332,12 @@
"label": "Antwortadresse automatisch wählen",
"description": "Beim Antworten die Absenderadresse automatisch auf die Identität umstellen, die die ursprüngliche Nachricht erhalten hat"
},
"reply_identity_match": {
"label": "Abgleich der Empfangsadresse",
"description": "Welche Empfangsadressen als eigene gelten. „Nur exakte Adresse“ wählt ausschließlich eine Ihrer eingerichteten Identitäten. „Gleiche Domain“ behandelt zusätzlich jede andere Adresse auf einer Ihrer Identitäts-Domains als Catch-all-Alias und ersetzt den Absender-Header damit. Wählen Sie die exakte Adresse, wenn solche Adressen Verteilerlisten und keine Aliasse sind.",
"exact": "Nur exakte Adresse",
"domain": "Jede Adresse auf meinen Domains"
},
"signature_position": {
"label": "Signaturposition",
"description": "Wo Ihre Signatur in Antworten und Weiterleitungen eingefügt wird. Über dem zitierten Text liest sie sich natürlich als Abschluss der Antwort; darunter bleibt die ursprüngliche Nachricht zusammenhängend.",
+7 -1
View File
@@ -1333,7 +1333,13 @@
},
"auto_select_reply_identity": {
"label": "Reply From Received Address",
"description": "When replying, send from the address the message was originally sent to. Matches identities first; for domain catch-all deliveries, rewrites the From header to the alias while sending through your primary identity."
"description": "When replying, send from the address the message was originally sent to. Your configured identities always match; whether other addresses on your domains count too (catch-all) is set by “Received Address Matching” below."
},
"reply_identity_match": {
"label": "Received Address Matching",
"description": "Which received addresses count as yours. Exact address only picks one of your configured identities. Same domain also treats any other address on one of your identity domains as a catch-all alias and rewrites the From header to it. Choose exact address if those addresses are distribution lists rather than aliases.",
"exact": "Exact address only",
"domain": "Any address on my domains"
},
"signature_position": {
"label": "Signature Position",
+6
View File
@@ -1327,6 +1327,12 @@
"label": "Seleccionar dirección de respuesta automáticamente",
"description": "Al responder, cambia automáticamente la dirección del remitente a la identidad que recibió el mensaje original"
},
"reply_identity_match": {
"label": "Coincidencia de la dirección de recepción",
"description": "Qué direcciones de recepción cuentan como tuyas. «Solo la dirección exacta» elige únicamente una de tus identidades configuradas; «mismo dominio» también trata cualquier otra dirección de tus dominios de identidad como un alias catch-all y reescribe la cabecera De con ella. Elige la dirección exacta si esas direcciones son listas de distribución.",
"exact": "Solo la dirección exacta",
"domain": "Cualquier dirección de mis dominios"
},
"signature_position": {
"label": "Posición de la firma",
"description": "Dónde insertar tu firma en respuestas y reenvíos. Encima del texto citado se lee de forma natural como cierre de la respuesta; debajo mantiene el mensaje original contiguo.",
+6
View File
@@ -1335,6 +1335,12 @@
"label": "پاسخ از آدرس دریافتی",
"description": "ارسال با آدرس دریافت شده"
},
"reply_identity_match": {
"label": "تطبیق نشانی دریافت",
"description": "کدام نشانی‌های دریافت به‌عنوان نشانی شما در نظر گرفته شوند. «فقط نشانی دقیق» تنها یکی از هویت‌های پیکربندی‌شدهٔ شما را انتخاب می‌کند؛ «همان دامنه» هر نشانی دیگری در دامنه‌های هویت شما را نیز نام مستعار catch-all در نظر می‌گیرد و سرآیند فرستنده را با آن بازنویسی می‌کند. اگر این نشانی‌ها فهرست توزیع هستند، نشانی دقیق را انتخاب کنید.",
"exact": "فقط نشانی دقیق",
"domain": "هر نشانی در دامنه‌های من"
},
"signature_position": {
"label": "موقعیت امضا",
"description": "محل درج امضا",
+6
View File
@@ -1327,6 +1327,12 @@
"label": "Sélection automatique de l'adresse de réponse",
"description": "Lors d'une réponse, bascule automatiquement l'adresse d'expédition vers l'identité qui a reçu le message d'origine"
},
"reply_identity_match": {
"label": "Correspondance de l'adresse de réception",
"description": "Quelles adresses de réception comptent comme les vôtres. « Adresse exacte uniquement » ne choisit qu'une de vos identités configurées ; « même domaine » traite aussi toute autre adresse de vos domaines d'identité comme un alias catch-all et y réécrit l'en-tête De. Choisissez l'adresse exacte si ces adresses sont des listes de diffusion.",
"exact": "Adresse exacte uniquement",
"domain": "Toute adresse de mes domaines"
},
"signature_position": {
"label": "Position de la signature",
"description": "Où insérer votre signature dans les réponses et les transferts. Au-dessus du texte cité, elle se lit naturellement comme la conclusion de la réponse ; en dessous, elle garde le message d'origine contigu.",
+6
View File
@@ -1286,6 +1286,12 @@
"label": "בחירה אוטומטית בכתובת תשובה",
"description": "בעת תשובה, העבר אוטומטית את הכתובת מאת לזהות שקיבלה את ההודעה במקור"
},
"reply_identity_match": {
"label": "התאמת כתובת הקבלה",
"description": "אילו כתובות קבלה נחשבות לשלך. „כתובת מדויקת בלבד“ בוחרת רק אחת מהזהויות שהגדרת; „אותו דומיין“ מתייחסת גם לכל כתובת אחרת בדומייני הזהויות שלך ככינוי catch-all ומשכתבת אליה את כותרת השולח. בחר בכתובת מדויקת אם כתובות אלה הן רשימות תפוצה.",
"exact": "כתובת מדויקת בלבד",
"domain": "כל כתובת בדומיינים שלי"
},
"attachment_click_action": {
"label": "קובץ מצורף לחץ על פעולה",
"description": "בחר אם לחיצה על קובץ מצורף תציג אותו בתצוגה מקדימה או תוריד אותו מיד",
+6
View File
@@ -1335,6 +1335,12 @@
"label": "Válasz a fogadott címről",
"description": "Válaszküldéskor arról a címről küldjön, amelyre az üzenet eredetileg érkezett. Először identitásokat keres; tartományi catch-all kézbesítések esetén átírja a Feladó fejlécet az aliasra, miközben az elsődleges identitáson keresztül küld."
},
"reply_identity_match": {
"label": "Fogadó cím egyeztetése",
"description": "Mely fogadó címek számítanak a sajátjának. A „Csak pontos cím” kizárólag a beállított identitásai közül választ; az „azonos domain” az identitás-domainjein lévő bármely más címet is catch-all aliasként kezeli, és arra írja át a Feladó fejlécet. Válassza a pontos címet, ha ezek a címek terjesztési listák.",
"exact": "Csak pontos cím",
"domain": "Bármely cím a domainjeimen"
},
"signature_position": {
"label": "Aláírás pozíciója",
"description": "Hova kerüljön az aláírás a válaszokban és továbbításokban. Az idézett szöveg felett természetes lezárásként olvasható; alatta az eredeti üzenet marad egybefüggő.",
+6
View File
@@ -1327,6 +1327,12 @@
"label": "Seleziona automaticamente l'indirizzo di risposta",
"description": "Quando rispondi, passa automaticamente l'indirizzo mittente all'identità che ha ricevuto il messaggio originale"
},
"reply_identity_match": {
"label": "Corrispondenza dell'indirizzo di ricezione",
"description": "Quali indirizzi di ricezione contano come tuoi. «Solo indirizzo esatto» sceglie soltanto una delle tue identità configurate; «stesso dominio» tratta anche qualsiasi altro indirizzo dei tuoi domini di identità come alias catch-all e riscrive con esso l'intestazione Da. Scegli l'indirizzo esatto se quegli indirizzi sono liste di distribuzione.",
"exact": "Solo indirizzo esatto",
"domain": "Qualsiasi indirizzo dei miei domini"
},
"signature_position": {
"label": "Posizione della firma",
"description": "Dove inserire la tua firma nelle risposte e negli inoltri. Sopra il testo citato si legge in modo naturale come chiusura della risposta; sotto mantiene il messaggio originale contiguo.",
+6
View File
@@ -1327,6 +1327,12 @@
"label": "返信元アドレスを自動選択",
"description": "返信時に、元のメッセージを受信したIDへ差出人アドレスを自動的に切り替えます"
},
"reply_identity_match": {
"label": "受信アドレスの照合",
"description": "どの受信アドレスを自分のものとみなすか。「完全一致のみ」は設定済みのアイデンティティからのみ選択します。「同じドメイン」はアイデンティティのドメイン上の他のアドレスもキャッチオールのエイリアスとして扱い、From ヘッダーをそのアドレスに書き換えます。それらのアドレスが配布リストの場合は完全一致を選択してください。",
"exact": "完全一致のみ",
"domain": "自分のドメイン上の任意のアドレス"
},
"signature_position": {
"label": "署名の位置",
"description": "返信や転送で署名を挿入する位置。引用テキストの上は返信の締めとして自然に読めます。下は元のメッセージを続けて表示します。",
+6
View File
@@ -1332,6 +1332,12 @@
"label": "답장 시 보내는 사람 자동 선택",
"description": "답장할 때 메일을 받았던 주소로 보내는 사람을 자동으로 변경해요"
},
"reply_identity_match": {
"label": "수신 주소 일치 방식",
"description": "어떤 수신 주소를 내 주소로 간주할지 정합니다. '정확한 주소만'은 설정된 ID 중에서만 선택합니다. '같은 도메인'은 ID 도메인의 다른 주소도 캐치올 별칭으로 간주하여 보낸 사람 헤더를 해당 주소로 바꿉니다. 해당 주소가 배포 목록이라면 정확한 주소를 선택하세요.",
"exact": "정확한 주소만",
"domain": "내 도메인의 모든 주소"
},
"signature_position": {
"label": "서명 위치",
"description": "답장과 전달에서 서명을 삽입할 위치. 인용된 텍스트 위에 두면 답장의 마무리처럼 자연스럽게 읽히고, 아래에 두면 원본 메시지가 이어져 보입니다.",
+6
View File
@@ -1327,6 +1327,12 @@
"label": "Automātiski izvēlēties atbildes adresi",
"description": "Atbildot automātiski izmantot to kontu, uz kuru vēstule tika saņemta"
},
"reply_identity_match": {
"label": "Saņemšanas adreses atbilstība",
"description": "Kuras saņemšanas adreses tiek uzskatītas par jūsu. „Tikai precīza adrese“ izvēlas vienīgi kādu no jūsu konfigurētajām identitātēm; „tas pats domēns“ arī jebkuru citu adresi jūsu identitāšu domēnos uzskata par catch-all aizstājvārdu un pārraksta ar to galveni „No“. Izvēlieties precīzu adresi, ja šīs adreses ir izplatīšanas saraksti.",
"exact": "Tikai precīza adrese",
"domain": "Jebkura adrese manos domēnos"
},
"signature_position": {
"label": "Paraksta novietojums",
"description": "Kur ievietot jūsu parakstu atbildēs un pārsūtīšanā. Virs citētā teksta tas dabiski lasās kā atbildes noslēgums; zem tā saglabā oriģinālo ziņojumu vienkopus.",
+6
View File
@@ -1335,6 +1335,12 @@
"label": "Хүлээн авсан хаягаас хариу бичих",
"description": "Хариу бичихдээ мессежийг анх илгээсэн хаягаар нь илгээнэ үү. Эхлээд таних тэмдэгтэй таарах; Домэйн бүх хүргэлтийн хувьд, таны үндсэн таниулбараар дамжуулан илгээхдээ From толгой хэсгийг өөр нэр рүү дахин бичнэ."
},
"reply_identity_match": {
"label": "Хүлээн авах хаягийн тохирол",
"description": "Хүлээн авах ямар хаягуудыг таных гэж үзэх. „Зөвхөн яг таарах хаяг“ нь зөвхөн таны тохируулсан identity-уудаас сонгоно; „ижил домэйн“ нь таны identity-ийн домэйн дээрх бусад хаягийг ч catch-all нэр болгон үзэж, Илгээгч толгойг тэр хаягаар солино. Эдгээр хаяг нь түгээлтийн жагсаалт бол яг таарах хаягийг сонгоно уу.",
"exact": "Зөвхөн яг таарах хаяг",
"domain": "Миний домэйн дээрх дурын хаяг"
},
"signature_position": {
"label": "Гарын үсэг зурах албан тушаал",
"description": "Хариулт болон дамжуулалтад гарын үсгээ хаана оруулах вэ. Иш татсан бичвэрийн дээрх хариултыг хаах нь ойлгомжтой; доор нь эх мессежийг залгаж хадгална.",
+6
View File
@@ -1335,6 +1335,12 @@
"label": "Svar fra mottatt adresse",
"description": "Når du svarer, bruker Bulwark adressen meldingen opprinnelig ble sendt til. Programmet ser først etter en samsvarende identitet. For meldinger til et oppsamlingsdomene brukes aliaset i Fra-feltet, mens sendingen går gjennom hovedidentiteten din."
},
"reply_identity_match": {
"label": "Samsvar for mottaksadresse",
"description": "Hvilke mottaksadresser som regnes som dine. «Kun nøyaktig adresse» velger bare en av dine oppsatte identiteter; «samme domene» behandler i tillegg enhver annen adresse på identitetsdomenene dine som et catch-all-alias og skriver om Fra-headeren til den. Velg nøyaktig adresse hvis disse adressene er distribusjonslister.",
"exact": "Kun nøyaktig adresse",
"domain": "Enhver adresse på mine domener"
},
"signature_position": {
"label": "Signaturposisjon",
"description": "Velg hvor signaturen skal settes inn i svar og videresendinger. Over den siterte teksten fungerer den som en naturlig avslutning på svaret. Under den siterte teksten holdes den opprinnelige meldingen samlet.",
+6
View File
@@ -1327,6 +1327,12 @@
"label": "Antwoordadres automatisch selecteren",
"description": "Schakel bij het beantwoorden automatisch het Van-adres om naar de identiteit die het oorspronkelijke bericht ontving"
},
"reply_identity_match": {
"label": "Overeenkomst van ontvangstadres",
"description": "Welke ontvangstadressen als de uwe gelden. „Alleen exact adres“ kiest uitsluitend een van uw ingestelde identiteiten; „zelfde domein“ behandelt ook elk ander adres op uw identiteitsdomeinen als catch-all-alias en herschrijft de Van-header ernaar. Kies exact adres als die adressen distributielijsten zijn.",
"exact": "Alleen exact adres",
"domain": "Elk adres op mijn domeinen"
},
"signature_position": {
"label": "Positie van handtekening",
"description": "Waar je handtekening in antwoorden en doorgestuurde berichten moet worden ingevoegd. Boven de geciteerde tekst leest natuurlijk als afsluiting van het antwoord; eronder houdt het originele bericht aaneengesloten.",
+6
View File
@@ -1332,6 +1332,12 @@
"label": "Automatycznie wybieraj adres odpowiedzi",
"description": "Podczas odpowiadania automatycznie przełączaj adres nadawcy na tożsamość, która pierwotnie otrzymała wiadomość"
},
"reply_identity_match": {
"label": "Dopasowanie adresu odbioru",
"description": "Które adresy odbioru są uznawane za Twoje. „Tylko dokładny adres” wybiera wyłącznie jedną ze skonfigurowanych tożsamości; „ta sama domena” traktuje dodatkowo każdy inny adres w domenach Twoich tożsamości jako alias catch-all i przepisuje na niego nagłówek Od. Wybierz dokładny adres, jeśli te adresy to listy dystrybucyjne.",
"exact": "Tylko dokładny adres",
"domain": "Dowolny adres w moich domenach"
},
"signature_position": {
"label": "Pozycja podpisu",
"description": "Gdzie wstawić podpis w odpowiedziach i wiadomościach przekazanych dalej. Nad cytowanym tekstem brzmi naturalnie jako zakończenie odpowiedzi; pod nim zachowuje oryginalną wiadomość w całości.",
+6
View File
@@ -1327,6 +1327,12 @@
"label": "Selecionar automaticamente o endereço de resposta",
"description": "Ao responder, muda automaticamente o endereço do remetente para a identidade que recebeu a mensagem original"
},
"reply_identity_match": {
"label": "Correspondência do endereço de receção",
"description": "Quais endereços de receção contam como seus. «Apenas endereço exato» escolhe somente uma das suas identidades configuradas; «mesmo domínio» trata também qualquer outro endereço dos seus domínios de identidade como alias catch-all e reescreve o cabeçalho De com ele. Escolha o endereço exato se esses endereços forem listas de distribuição.",
"exact": "Apenas endereço exato",
"domain": "Qualquer endereço dos meus domínios"
},
"signature_position": {
"label": "Posição da assinatura",
"description": "Onde inserir a sua assinatura em respostas e encaminhamentos. Acima do texto citado lê-se naturalmente como fecho da resposta; abaixo mantém a mensagem original contígua.",
+6
View File
@@ -1335,6 +1335,12 @@
"label": "Răspunde de la adresa de la care a fost primit mesajul",
"description": "Când răspundeți, trimiteți mesajul de la adresa la care a fost trimis inițial. Se potrivesc mai întâi identitățile; pentru livrările de tip „catch-all” ale domeniului, antetul „De la” este rescris cu aliasul, în timp ce trimiterea se face prin identitatea dvs. principală."
},
"reply_identity_match": {
"label": "Potrivirea adresei de primire",
"description": "Care adrese de primire sunt considerate ale tale. „Doar adresa exactă” alege numai una dintre identitățile configurate; „același domeniu” tratează și orice altă adresă de pe domeniile identităților tale ca alias catch-all și rescrie antetul De la cu ea. Alege adresa exactă dacă aceste adrese sunt liste de distribuție.",
"exact": "Doar adresa exactă",
"domain": "Orice adresă de pe domeniile mele"
},
"signature_position": {
"label": "Poziția semnăturii",
"description": "Unde să inserați semnătura în răspunsuri și redirecționări. Deasupra textului citat, semnătura se citește în mod natural ca o încheiere a răspunsului; sub text, mesajul original rămâne contiguu.",
+6
View File
@@ -1327,6 +1327,12 @@
"label": "Автоматически выбирать адрес для ответа",
"description": "При ответе автоматически переключать адрес отправителя на ту учетную запись, которая получила исходное сообщение"
},
"reply_identity_match": {
"label": "Сопоставление адреса получения",
"description": "Какие адреса получения считать вашими. «Только точный адрес» выбирает исключительно одну из настроенных личностей; «тот же домен» также считает любой другой адрес на доменах ваших личностей catch-all-псевдонимом и подставляет его в заголовок «От». Выберите точный адрес, если такие адреса являются списками рассылки.",
"exact": "Только точный адрес",
"domain": "Любой адрес на моих доменах"
},
"signature_position": {
"label": "Положение подписи",
"description": "Куда вставлять подпись в ответах и пересылке. Над цитируемым текстом она читается естественно как завершение ответа; под ним сохраняет целостность исходного сообщения.",
+6
View File
@@ -1335,6 +1335,12 @@
"label": "Odpovedať z prijatej adresy",
"description": "Pri odpovedaní automaticky prepnúť adresu odosielateľa na identitu, ktorá pôvodne prijala správu"
},
"reply_identity_match": {
"label": "Porovnávanie prijímacej adresy",
"description": "Ktoré prijímacie adresy sa považujú za vaše. „Iba presná adresa“ vyberie len jednu z vašich nastavených identít; „rovnaká doména“ navyše považuje akúkoľvek inú adresu na doménach vašich identít za catch-all alias a prepíše na ňu hlavičku Od. Ak sú tieto adresy distribučné zoznamy, zvoľte presnú adresu.",
"exact": "Iba presná adresa",
"domain": "Akákoľvek adresa na mojich doménach"
},
"signature_position": {
"label": "Pozícia podpisu",
"description": "Kam vložiť podpis v odpovediach a preposlaniach.",
+6
View File
@@ -1332,6 +1332,12 @@
"label": "Yanıt Adresini Otomatik Seç",
"description": "Yanıtlarken, Kimden adresini iletiyi başlangıçta alan kimliğe otomatik olarak değiştir"
},
"reply_identity_match": {
"label": "Alıcı adresi eşleştirme",
"description": "Hangi alıcı adreslerinin sizin sayılacağı. „Yalnızca tam adres“ sadece yapılandırılmış kimliklerinizden birini seçer; „aynı alan adı“ ayrıca kimlik alan adlarınızdaki diğer tüm adresleri catch-all takma adı olarak değerlendirir ve Kimden başlığını buna göre yeniden yazar. Bu adresler dağıtım listesiyse tam adresi seçin.",
"exact": "Yalnızca tam adres",
"domain": "Alan adlarımdaki herhangi bir adres"
},
"signature_position": {
"label": "İmza konumu",
"description": "Yanıtlarda ve iletmelerde imzanızın nereye ekleneceği. Alıntılanan metnin üzerinde, yanıt için doğal bir kapanış olarak okunur; altında ise orijinal mesajı bir bütün hâlinde tutar.",
+6
View File
@@ -1332,6 +1332,12 @@
"label": "Автоматичний вибір адреси для відповіді",
"description": "Під час відповіді автоматично змінюйте адресу відправника на особу, яка спочатку отримала повідомлення"
},
"reply_identity_match": {
"label": "Зіставлення адреси отримання",
"description": "Які адреси отримання вважати вашими. «Лише точна адреса» вибирає виключно одну з налаштованих ідентичностей; «той самий домен» також вважає будь-яку іншу адресу на доменах ваших ідентичностей catch-all-псевдонімом і підставляє її в заголовок «Від». Виберіть точну адресу, якщо такі адреси є списками розсилки.",
"exact": "Лише точна адреса",
"domain": "Будь-яка адреса на моїх доменах"
},
"signature_position": {
"label": "Розташування підпису",
"description": "Куди вставляти підпис у відповідях і пересиланнях. Над цитованим текстом читається природно як завершення відповіді; під ним зберігає цілісність оригінального повідомлення.",
+6
View File
@@ -1335,6 +1335,12 @@
"label": "使用收件地址回覆",
"description": "回覆時,使用原始郵件的收件地址寄送。系統會先比對寄件身分;若是網域 catch-all 收件,則透過主要寄件身分傳送,但將 From 標頭改寫為別名地址。"
},
"reply_identity_match": {
"label": "接收地址比對",
"description": "哪些接收地址算作您的地址。「僅精確地址」只從已設定的身分中選擇;「相同網域」還會將身分網域下的其他任何地址視為 catch-all 別名,並將寄件者標頭改寫為該地址。如果這些地址是通訊群組清單,請選擇精確地址。",
"exact": "僅精確地址",
"domain": "我的網域下的任意地址"
},
"signature_position": {
"label": "簽名檔位置",
"description": "在回覆與轉寄中插入簽名檔的位置。放在引用文字上方可自然作為回覆結尾;放在下方則能保持原始郵件連續。",
+6
View File
@@ -1332,6 +1332,12 @@
"label": "自动选择回复地址",
"description": "回复时自动将发件人地址切换为最初收到该邮件的身份"
},
"reply_identity_match": {
"label": "接收地址匹配",
"description": "哪些接收地址算作您的地址。“仅精确地址”只从已配置的身份中选择;“相同域名”还会将身份域名下的其他任何地址视为 catch-all 别名,并将发件人标头改写为该地址。如果这些地址是分发列表,请选择精确地址。",
"exact": "仅精确地址",
"domain": "我的域名下的任意地址"
},
"signature_position": {
"label": "签名位置",
"description": "在回复和转发中插入签名的位置。位于引用文本上方时,可作为回复的自然结尾;位于下方时,保持原始邮件连贯。",
+4
View File
@@ -102,6 +102,7 @@ export type ListDensity = Density;
export type DeleteAction = 'trash' | 'trash-and-read' | 'permanent';
export type ReplyMode = 'reply' | 'replyAll';
export type SignaturePosition = 'above_quote' | 'below_quote';
export type ReplyIdentityMatch = 'exact' | 'domain';
/** How to handle an incoming Disposition-Notification-To (read-receipt) request. */
export type ReadReceiptResponse = 'ask' | 'always' | 'never';
export type DateFormat = 'smart' | 'relative' | 'full';
@@ -343,6 +344,7 @@ interface SettingsState {
sendConfirmation: boolean;
defaultReplyMode: ReplyMode;
autoSelectReplyIdentity: boolean;
replyIdentityMatch: ReplyIdentityMatch; // With autoSelectReplyIdentity on: 'exact' = configured identities only, 'domain' = also same-domain catch-all addresses (rewrites From) #1000
plainTextMode: boolean; // Send plain text only (no rich text editor)
rtlEditingSupport: boolean; // Show a per-paragraph LTR/RTL direction control in the composer (Gmail-style)
subAddressDelimiter: string; // Character separating user from tag (e.g. "user+tag@")
@@ -582,6 +584,7 @@ const DEFAULT_SETTINGS = {
sendConfirmation: false,
defaultReplyMode: 'reply' as ReplyMode,
autoSelectReplyIdentity: false,
replyIdentityMatch: 'domain' as ReplyIdentityMatch,
plainTextMode: false,
rtlEditingSupport: false,
subAddressDelimiter: DEFAULT_SUB_ADDRESS_DELIMITER,
@@ -808,6 +811,7 @@ export const useSettingsStore = create<SettingsState>()(
sendConfirmation: state.sendConfirmation,
defaultReplyMode: state.defaultReplyMode,
autoSelectReplyIdentity: state.autoSelectReplyIdentity,
replyIdentityMatch: state.replyIdentityMatch,
plainTextMode: state.plainTextMode,
rtlEditingSupport: state.rtlEditingSupport,
subAddressDelimiter: state.subAddressDelimiter,