import type { IJMAPClient } from '@/lib/jmap/client-interface'; import type { Email, Identity } from '@/lib/jmap/types'; import { computeReplyThreadingHeaders } from '@/lib/email-threading'; import { findDraftIdentityId, findReplyIdentityId } from '@/lib/reply-identity'; import { buildReplyRecipients, isSelfSent } from '@/lib/reply-recipients'; import { buildReplySubject } from '@/lib/subject-prefix'; type ReplySource = Pick; export interface QuickReplyEnvelope { recipients: string[]; /** The identity the reply goes out as, the primary one when none matches. */ sendingIdentity: Identity | undefined; primaryIdentity: Identity | undefined; } /** * Who a quick reply goes to and from whom: the composer's addressing rules * (Reply-To over From; for our own messages in a thread, the original * recipients instead of ourselves, #703), from the address the message was * delivered to, or for our own message the identity it was sent from. * * Quick reply resolves the user's OWN identities only. It deliberately does * NOT take the domain catch-all `From:` rewrite that the full composer * offers: this surface is a bare text box with no From row, so a rewritten * From would be applied with nothing on screen to show it - or to correct * it. */ export function quickReplyEnvelope(email: ReplySource, identities: Identity[]): QuickReplyEnvelope { const ownIdentityEmails = identities.map((i) => i.email).filter(Boolean); const replySource = { from: email.from, replyToAddresses: email.replyTo, to: email.to, cc: email.cc }; const recipients = buildReplyRecipients(replySource, 'reply', ownIdentityEmails).to .map((r) => r.email) .filter((address): address is string => Boolean(address)); const primaryIdentity = identities[0]; const selfSentIdentityId = isSelfSent(replySource, ownIdentityEmails) ? findDraftIdentityId(identities, email.from?.[0]) : null; const sendingIdentityId = selfSentIdentityId ?? findReplyIdentityId(identities, { to: email.to, cc: email.cc, bcc: email.bcc }); const sendingIdentity = (sendingIdentityId ? identities.find((i) => i.id === sendingIdentityId) : undefined) || primaryIdentity; return { recipients, sendingIdentity, primaryIdentity }; } /** * Saves the text typed into a quick reply as a draft reply, before an * automatic sign-out (lib/compose-flush-registry.ts). Plain text, threaded * like the reply would be; the signature is added when it is sent. */ export async function saveQuickReplyDraft(input: { client: IJMAPClient; email: Email; identities: Identity[]; body: string; replyPrefix: string; }): Promise { const { client, email, identities, body, replyPrefix } = input; const { recipients, sendingIdentity } = quickReplyEnvelope(email, identities); const threading = computeReplyThreadingHeaders({ messageId: email.messageId, references: email.references }); await client.createDraft( recipients, buildReplySubject(email.subject || '', replyPrefix), body, undefined, undefined, sendingIdentity?.id, sendingIdentity?.email, undefined, undefined, sendingIdentity?.name || undefined, undefined, threading?.inReplyTo, threading?.references, ); }