Description
When an identity has an HTML signature configured, the compose flow has several related issues. The most visible one: opening a new mail while another mail is selected shows the composer with no signature at all, and the body cannot be edited into a useful state until the user navigates away from the selected mail.
Affected files
components/email/email-composer.tsx — getInitialBody(), signature preview, identity-switch effect, unmount auto-save effect
app/(main)/[locale]/page.tsx — composer entry points (handleReply, handleReplyAll, handleForward, the four onCompose callbacks)
Bugs A–D
A) "New mail" with a selected mail opens empty (no signature)
The composer prop replyTo is derived from selectedEmail (page.tsx:2650+) and so non-empty even in compose mode. Inside getInitialBody() every reply branch is gated on mode === 'reply' / 'replyAll' / 'forward', none match, and the function returns the empty prefix. The new-mail signature embedding never runs.
B) Signature on new mail is non-editable
Even when replyTo is empty, the signature was rendered as a <div dangerouslySetInnerHTML> below the editor (email-composer.tsx:~1957) instead of being embedded in the body. The user could not delete or edit it. Replies with signaturePosition === 'above_quote' embedded the signature correctly via buildEmbeddedSignatureHtml — only the new-mail path was inconsistent.
C) Composer keeps previous state on Reply / "New mail"
All "New mail" entries (keyboard shortcut, sidebar, floating PenSquare button, mobile viewer) only called setComposerMode('compose') + setShowComposer(true), never setComposerSessionId++. Because <EmailComposer key={composerSessionId} />, React kept the previous compose state. The same omission existed in handleReply / handleReplyAll / handleForward, so e.g. "Reply on X → discard → Reply" rendered a stale composer instead of a fresh reply.
D) Discard resurrects a stale pendingDraft
The unmount auto-save useEffect (email-composer.tsx:~695) fires whenever isDirtyRef.current === true. handleDiscardAndClose clears stateRef but does not silence the effect — so after discarding, the cleanup runs onSaveState({...emptyState, mode:'reply', replyTo:X}), which the parent stores as pendingDraft. The next "New mail" click then renders the composer with that stale state instead of a clean one.
Suggested fix
A self-contained patch addressing all four:
-
getInitialBody() branches on mode === 'compose' first and ignores any leftover replyTo, embedding the signature directly into the body. This is the root fix for A and B at once.
-
shouldEmbedSignatureInNewMail = mode === 'compose' && hasInitialSignature drives both the body embedding and the suppression of the redundant signature preview below the editor.
-
The identity-switch useEffect that swaps the embedded signature now also runs in compose mode, so changing identity refreshes the embedded signature in new mails.
-
All composer entry points — handleReply, handleReplyAll, handleForward and the four onCompose handlers — bump composerSessionId and reset pendingDraft so each new compose starts from a clean slate (fixes C).
-
A new explicitCloseRef is set by cleanClose / handleSaveDraftAndClose / handleDiscardAndClose and checked at the start of the unmount auto-save effect, so explicit closes no longer write pendingDraft (fixes D).
Reference implementation (diff against 1.7.1 baseline):
dealerweb/bulwark-webmail@main...fix/signature-editable-in-new-mail
Steps to Reproduce
Configure an HTML signature in an identity (Settings → Identities).
2. Open any mail in the viewer to select it.
3. Click the floating "New mail" button (or use the sidebar / keyboard shortcut).
4. The composer opens completely empty — no signature.
5. Bonus: Reply on a mail → delete the signature in the reply → Discard → "New mail" → still empty.
6. Bonus: Reply → Discard → Reply → renders as a blank new mail instead of a reply.
Expected Behavior
"New mail" always opens a fresh composer with the identity's signature embedded in the editor body (editable, removable). Discarding a draft never bleeds state into the next compose session.
Actual Behavior
- "New mail" while a mail is selected → composer is empty, no signature, body unusable.
- Reply → discard → "New mail" → empty (stale pendingDraft from the discarded reply).
- Reply → discard → Reply → blank new-mail composer instead of reply.
- New mail (no mail selected) → signature appears as a non-editable preview below the editor.
Bulwark Version
1.7.1
Stalwart Mail Server Version
0.16.5
Browser
Chrome / Chromium
Operating System
Windows
Screenshots / Screen Recording
No response
Relevant Logs or Error Output
Additional Context
No response
Description
When an identity has an HTML signature configured, the compose flow has several related issues. The most visible one: opening a new mail while another mail is selected shows the composer with no signature at all, and the body cannot be edited into a useful state until the user navigates away from the selected mail.
Affected files
components/email/email-composer.tsx—getInitialBody(), signature preview, identity-switch effect, unmount auto-save effectapp/(main)/[locale]/page.tsx— composer entry points (handleReply,handleReplyAll,handleForward, the fouronComposecallbacks)Bugs A–D
A) "New mail" with a selected mail opens empty (no signature)
The composer prop
replyTois derived fromselectedEmail(page.tsx:2650+) and so non-empty even in compose mode. InsidegetInitialBody()every reply branch is gated onmode === 'reply' / 'replyAll' / 'forward', none match, and the function returns the emptyprefix. The new-mail signature embedding never runs.B) Signature on new mail is non-editable
Even when
replyTois empty, the signature was rendered as a<div dangerouslySetInnerHTML>below the editor (email-composer.tsx:~1957) instead of being embedded in the body. The user could not delete or edit it. Replies withsignaturePosition === 'above_quote'embedded the signature correctly viabuildEmbeddedSignatureHtml— only the new-mail path was inconsistent.C) Composer keeps previous state on Reply / "New mail"
All "New mail" entries (keyboard shortcut, sidebar, floating PenSquare button, mobile viewer) only called
setComposerMode('compose')+setShowComposer(true), neversetComposerSessionId++. Because<EmailComposer key={composerSessionId} />, React kept the previous compose state. The same omission existed inhandleReply/handleReplyAll/handleForward, so e.g. "Reply on X → discard → Reply" rendered a stale composer instead of a fresh reply.D) Discard resurrects a stale pendingDraft
The unmount auto-save
useEffect(email-composer.tsx:~695) fires wheneverisDirtyRef.current === true.handleDiscardAndCloseclearsstateRefbut does not silence the effect — so after discarding, the cleanup runsonSaveState({...emptyState, mode:'reply', replyTo:X}), which the parent stores aspendingDraft. The next "New mail" click then renders the composer with that stale state instead of a clean one.Suggested fix
A self-contained patch addressing all four:
getInitialBody()branches onmode === 'compose'first and ignores any leftoverreplyTo, embedding the signature directly into the body. This is the root fix for A and B at once.shouldEmbedSignatureInNewMail = mode === 'compose' && hasInitialSignaturedrives both the body embedding and the suppression of the redundant signature preview below the editor.The identity-switch
useEffectthat swaps the embedded signature now also runs in compose mode, so changing identity refreshes the embedded signature in new mails.All composer entry points —
handleReply,handleReplyAll,handleForwardand the fouronComposehandlers — bumpcomposerSessionIdand resetpendingDraftso each new compose starts from a clean slate (fixes C).A new
explicitCloseRefis set bycleanClose/handleSaveDraftAndClose/handleDiscardAndCloseand checked at the start of the unmount auto-save effect, so explicit closes no longer writependingDraft(fixes D).Reference implementation (diff against 1.7.1 baseline):
dealerweb/bulwark-webmail@main...fix/signature-editable-in-new-mail
Steps to Reproduce
Configure an HTML signature in an identity (Settings → Identities).
2. Open any mail in the viewer to select it.
3. Click the floating "New mail" button (or use the sidebar / keyboard shortcut).
4. The composer opens completely empty — no signature.
5. Bonus: Reply on a mail → delete the signature in the reply → Discard → "New mail" → still empty.
6. Bonus: Reply → Discard → Reply → renders as a blank new mail instead of a reply.
Expected Behavior
"New mail" always opens a fresh composer with the identity's signature embedded in the editor body (editable, removable). Discarding a draft never bleeds state into the next compose session.
Actual Behavior
Bulwark Version
1.7.1
Stalwart Mail Server Version
0.16.5
Browser
Chrome / Chromium
Operating System
Windows
Screenshots / Screen Recording
No response
Relevant Logs or Error Output
Additional Context
No response