feat(compose): From override + catch-all auto-reply (fixes #246)
Adds an Override toggle in the composer's From row. When enabled, name and address become free-text inputs. Mail is still submitted through the selected identity, but the outgoing message's From: header — and the SMTP envelope MAIL FROM when different — is set from the override. The existing "Auto-select Reply Address" setting is extended: if the incoming message was addressed to an alias on a domain that matches one of your identities but isn't itself an identity (classic domain catch- all), it now auto-enables Override and pre-fills the alias. Quick reply honors the same resolution. The setting is relabeled to reflect the broader behavior. JMAP: client.sendEmail gains an optional envelopeMailFrom; when set, the EmailSubmission includes an explicit envelope with that mailFrom and the to/cc/bcc as rcptTo so header-From and envelope can diverge (JMAP §7.3). S/MIME: override is incompatible with sign/encrypt and is refused with a clear error — signing a different visible From from the identity's certificate Subject would produce messages clients reject. Tests: resolveReplyFrom covers exact match, sub-address stripping, catch-all detection, identity preference, and foreign-domain null.
This commit is contained in:
committed by
Linus Rath
parent
2d7e24b513
commit
b0640c9ecc
@@ -146,6 +146,7 @@ export interface IJMAPClient {
|
||||
attachments?: Array<{ blobId: string; name: string; type: string; size: number; disposition?: 'attachment' | 'inline'; cid?: string }>,
|
||||
inReplyTo?: string[],
|
||||
references?: string[],
|
||||
envelopeMailFrom?: string,
|
||||
): Promise<void>;
|
||||
|
||||
sendImipReply(opts: {
|
||||
|
||||
+19
-3
@@ -2089,7 +2089,8 @@ export class JMAPClient implements IJMAPClient {
|
||||
htmlBody?: string,
|
||||
attachments?: Array<{ blobId: string; name: string; type: string; size: number; disposition?: 'attachment' | 'inline'; cid?: string }>,
|
||||
inReplyTo?: string[],
|
||||
references?: string[]
|
||||
references?: string[],
|
||||
envelopeMailFrom?: string
|
||||
): Promise<void> {
|
||||
const emailId = `send-${Date.now()}`;
|
||||
const mailboxes = await this.getMailboxes();
|
||||
@@ -2185,6 +2186,21 @@ export class JMAPClient implements IJMAPClient {
|
||||
},
|
||||
};
|
||||
|
||||
// When an explicit envelope MAIL FROM is provided (header From ≠ envelope,
|
||||
// e.g. sending from a domain-catch-all alias without a dedicated Identity),
|
||||
// set the EmailSubmission envelope explicitly. JMAP §7.3: when `envelope`
|
||||
// is omitted the server derives mailFrom from the Identity.
|
||||
const submissionCreate = (submissionId: string): Record<string, unknown> => {
|
||||
const create: Record<string, unknown> = { emailId: `#${emailId}`, identityId: finalIdentityId };
|
||||
if (envelopeMailFrom) {
|
||||
create.envelope = {
|
||||
mailFrom: { email: envelopeMailFrom },
|
||||
rcptTo: [...to, ...(cc || []), ...(bcc || [])].map((email) => ({ email })),
|
||||
};
|
||||
}
|
||||
return { [submissionId]: create };
|
||||
};
|
||||
|
||||
if (draftId) {
|
||||
// Destroy the old draft and create a new email with the final body
|
||||
methodCalls.push(["Email/set", {
|
||||
@@ -2197,7 +2213,7 @@ export class JMAPClient implements IJMAPClient {
|
||||
}, "1"]);
|
||||
methodCalls.push(["EmailSubmission/set", {
|
||||
accountId: this.accountId,
|
||||
create: { "1": { emailId: `#${emailId}`, identityId: finalIdentityId } },
|
||||
create: submissionCreate("1"),
|
||||
onSuccessUpdateEmail,
|
||||
}, "2"]);
|
||||
} else {
|
||||
@@ -2207,7 +2223,7 @@ export class JMAPClient implements IJMAPClient {
|
||||
}, "0"]);
|
||||
methodCalls.push(["EmailSubmission/set", {
|
||||
accountId: this.accountId,
|
||||
create: { "1": { emailId: `#${emailId}`, identityId: finalIdentityId } },
|
||||
create: submissionCreate("1"),
|
||||
onSuccessUpdateEmail,
|
||||
}, "1"]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user