Merge remote-tracking branch 'origin/main' into feature/scheduled-send

# Conflicts:
#	app/[locale]/page.tsx
#	components/email/email-composer.tsx
#	components/email/email-viewer.tsx
This commit is contained in:
Lucas Gaitzsch
2026-05-07 18:01:41 +02:00
120 changed files with 7033 additions and 4882 deletions
+22 -1
View File
@@ -997,7 +997,7 @@ export function EmailComposer({
return '';
};
// RFC 5322 §3.6.4 threading only continues the chain on a reply, not a forward.
// RFC 5322 §3.6.4 threading - only continues the chain on a reply, not a forward.
const threadingHeaders = (mode === 'reply' || mode === 'replyAll')
? computeReplyThreadingHeaders(replyTo)
: null;
@@ -1015,6 +1015,26 @@ export function EmailComposer({
try {
const effectiveSendAt = await getEffectiveSendAt(sendAt);
// Let plugins veto the send (external-mail warning, mistyped-domain
// guards, etc.). Returning false from any handler aborts before either
// the S/MIME or standard JMAP path runs.
const sendablePreview: OutgoingEmail = {
to: toAddresses,
cc: ccAddresses,
bcc: bccAddresses,
subject,
htmlBody: finalHtmlBody || '',
textBody: finalBody,
identityId: currentIdentity?.id || '',
fromEmail,
attachments: attachments
.filter(att => att.blobId && !att.uploading && !att.error)
.map(a => ({ name: a.name, type: a.type || 'application/octet-stream', size: a.size })),
inReplyTo: threadingHeaders?.inReplyTo?.[0],
};
const sendAllowed = await emailHooks.onBeforeEmailSend.intercept(sendablePreview);
if (!sendAllowed) return;
// S/MIME send pipeline: build raw MIME → sign → encrypt → sendRawEmail
if ((smimeSign_ || smimeEncrypt_) && client && currentIdentity?.id) {
// 1. Resolve S/MIME key
@@ -1159,6 +1179,7 @@ export function EmailComposer({
htmlBody: finalHtmlBody || '',
textBody: finalBody,
identityId: currentIdentity?.id || '',
fromEmail,
attachments: uploadedAttachments.map(a => ({ name: a.name, type: a.type, size: a.size })),
inReplyTo: threadingHeaders?.inReplyTo?.[0],
};