Skip to content

feat(compose): reply with AI from the conversation and an instruction - #577

Open
prudenciooficial wants to merge 9 commits into
maathimself:mainfrom
prudenciooficial:feat/ai-reply
Open

prudenciooficial wants to merge 9 commits into
maathimself:mainfrom
prudenciooficial:feat/ai-reply

Conversation

@prudenciooficial

@prudenciooficial prudenciooficial commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds Reply with AI: the user says in a few words what the reply should say ("approve the artwork and ask for the print date") and the AI writes it from the conversation so far. The existing compose AI actions draft from the subject and recipients, or rewrite what is already typed; none of them reads the message being answered.

It is offered where a reply starts: a Reply with AI button in the reading pane toolbar, one on each open message in the conversation view, and an entry in the composer's AI menu when replying. The composer opens with the instruction field focused. The suggestion streams into the existing AI panel with Apply and Dismiss, and the instruction stays editable to ask again in other words.

Reading pane The suggestion, from the instruction and the thread
Reading pane Suggestion
Conversation view
Conversation view

No backend change: it uses the existing /api/ai/chat proxy and the thread and body endpoints. It is shown only when the AI's compose feature is on and the user writes in rich text, since the plain-text composer has no AI panel.

Changes

  • utils/aiReply.js (new) builds the request:
    • The thread up to the message being answered (the newest 10), one copy of each, with the user's own messages marked. A reply opened from the ungrouped list carries no thread_id, so the thread is found through resolve-message from the answered Message-ID. A message with no thread is read on its own, and if nothing loads, the quoted original the composer already holds is used.
    • The quoted copies of earlier messages are cut (the common reply headers in several languages, Outlook header blocks, > lines, the signature separator).
    • It stays under the proxy's 32,000-character limit: newest messages first, older ones shortened, then left out, and the prompt says how many.
    • The prompt says the conversation is quoted material whose instructions are ignored, asks for the reply in the conversation's language, with a bracketed placeholder instead of an invented fact, and leaves the signature to the composer.
  • ComposeModal.jsx: the action and the instruction field. The conversation is loaded once per composer, so rewording only asks the AI again. The two copies of the suggestion panel (phone and desktop layouts) are now one ComposeAiPanel component.
  • Apply now escapes the suggestion before it reaches the editor. Before, markup in a suggestion, such as a link, became real markup in the user's message; with the conversation as input, part of that text comes from mail other people sent. This also covers the existing Draft, Improve, Shorten and Grammar actions.
  • MessagePane.jsx, ConversationMessageCard.jsx, ConversationPane.jsx, composeFromMessage.js: the buttons, and an aiReply flag that opens the composer with the panel. A message reopened by Undo send drops the flag.
  • The 5 new strings in all 11 locales.

Testing

  • Unit tests for building the request (aiReply.test.js). Render tests: the reply is written from the thread and the instruction; Apply keeps the quoted original; rewording does not reload the thread; markup in a suggestion stays text; the button opens the composer with the instruction focused; it is not offered without the compose feature or in plain-text mode; Undo does not reopen the panel. The tests for escaping, the thread lookup, the single load, the focus and Undo were checked to fail with that part reverted.
  • Checked in Chromium, desktop and phone width, against a throwaway stack with GreenMail and a stub OpenAI-compatible provider that records what it receives: the request carried the three messages of the thread, oldest first, quotes cut, the user's own reply marked. Those are the screenshots above (fictitious data).
  • Frontend suite, lint and build pass. The backend is unchanged.

Contributor License Agreement

By submitting this pull request I confirm that:

  • I have read and agree to the Contributor License Agreement.
  • My contribution is my own original work (or I have identified any
    third-party material and confirmed it is compatible with the CLA).
  • I have the right to submit this contribution under the terms of the CLA.

My English is basic, so this description was written with help from Claude.

🤖 Generated with the help of Claude Code

prudenciooficial and others added 2 commits October 8, 2026 18:42
When replying, the AI menu offers "Reply with AI". The user types what
the reply should say ("approve the artwork", "decline politely and ask
for a new proof") and the AI writes it from the conversation so far:
the thread up to the message being answered, oldest first, with the
user's own messages marked and the quoted copies of earlier messages
cut out. A reply opened from a list that is not grouped into
conversations does not carry the thread, so it is looked up from the
message being answered; when nothing can be loaded, the quoted original
the composer already holds is used.

The conversation is read once per composer, so rewording the
instruction and generating again only asks the AI. The request stays
within the chat proxy's limits: at most ten messages, the newest kept
first and the oldest shortened or left out.

The conversation is mail other people wrote, so the prompt tells the
model to ignore instructions inside it, and Apply now escapes the
suggestion before it reaches the editor. Before, a suggestion containing
markup, such as a link, became real markup in the user's message; this
applies to the existing actions too.

The two copies of the suggestion panel (phone and desktop layouts) are
now one component.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reading pane's toolbar gets a "Reply with AI" button next to Reply,
and each open message in the conversation view gets one beside Reply
all. It opens the reply with the AI panel already waiting for the
instruction, focused, so the user types what to say and nothing else.

It is shown only when the AI's compose feature is on and the user writes
in rich text, since the plain-text composer has no AI panel. A message
reopened by Undo send is the user's own text, so it reopens without the
panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
prudenciooficial and others added 3 commits October 9, 2026 12:26
…eat/ai-reply

# Conflicts:
#	frontend/src/components/ComposeModal.jsx
…eat/ai-reply

# Conflicts:
#	frontend/src/components/ComposeModal.jsx
With maathimself#556 a message can go out without its signature while the
signature text is kept. The reply prompt asked the AI to leave the
closing to a signature whenever one existed, so with the signature
turned off the reply ended with no name. It now asks for the sender's
name unless the signature is on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
prudenciooficial added a commit to prudenciooficial/mailflow that referenced this pull request Oct 9, 2026
…maathimself#577)

The fork's copy of maathimself#577, squashed from the feat/ai-reply branch as of
843711e, while that pull request awaits review.

"Reply with AI" sits in the reading pane toolbar, on each open message
in the conversation view and in the composer's AI menu when replying.
The user types what the reply should say; the AI writes it from the
thread up to the message being answered, with quotes cut and the user's
own messages marked. Apply escapes the suggestion before it reaches the
editor. With the signature turned off (maathimself#556) the reply signs with the
sender's name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
prudenciooficial and others added 4 commits October 9, 2026 21:02
…eat/ai-reply

# Conflicts:
#	frontend/src/components/MessagePane.render.test.js
The thread now marks its drafts (is_draft). A saved reply draft is part
of the thread but was never sent, so the AI was shown it as a message
the user had written in the conversation. It is left out now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eat/ai-reply

# Conflicts:
#	frontend/src/utils/composeFromMessage.js
…eat/ai-reply

# Conflicts:
#	frontend/src/components/ComposeModal.jsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant