Skip to content

[PB-1966]: refactor/reply a message - #96

Merged
xabg2 merged 2 commits into
masterfrom
refactor/reply-a-message
Jul 21, 2026
Merged

[PB-1966]: refactor/reply a message#96
xabg2 merged 2 commits into
masterfrom
refactor/reply-a-message

Conversation

@xabg2

@xabg2 xabg2 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Replies to an email were showing up as separate, standalone items in the inbox instead of being grouped under the original conversation. We refactored the flow to reply an email. What we did:

  • New dedicated "reply" endpoint.
  • Every message now gets a stable identifier. Each message we send gets its own, so that any future reply — including a reply to your own reply — can be correctly linked back into the conversation.
  • Consistent identifiers between what's delivered and what's saved. The copy saved in your "Sent" folder now shares the exact same identifier as the message that actually went out, so conversations stay linked on both sides.

Summary by CodeRabbit

  • New Features

    • Added support for replying directly to emails.
    • Replies automatically preserve conversation threading and generate a consistent Re: subject when no subject is provided.
    • Supports both internal and external delivery modes.
    • Sent copies of externally delivered emails retain the provider-assigned message ID.
  • Bug Fixes

    • Improved handling of missing or invalid parent email message IDs with a clear error response.
    • Enhanced email threading and mailbox mapping behavior.

@xabg2
xabg2 requested a review from apsantiso July 21, 2026 11:45
@xabg2 xabg2 self-assigned this Jul 21, 2026
@xabg2 xabg2 added the enhancement New feature or request label Jul 21, 2026
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Email reply support now includes a dedicated controller route, reply DTOs, subject/thread resolution, Message-ID generation and reuse, JMAP threading updates, provider error handling, and expanded tests for reply and mapping behavior.

Changes

Email reply threading and delivery

Layer / File(s) Summary
Reply contracts and threading helpers
src/modules/email/email.dto.ts, src/modules/email/email.types.ts, src/modules/email/threading.ts, src/modules/email/threading.spec.ts
Reply requests omit the parent ID, subjects are optional, threading headers include the parent subject, Message-IDs are generated, and Re: prefixes are normalized and tested.
Reply endpoint and service flow
src/modules/email/email.controller.ts, src/modules/email/email.controller.spec.ts, src/modules/email/email.service.ts, src/modules/email/email.service.spec.ts
The controller delegates POST :id/reply requests to replyEmail; the service resolves parent threading, derives subjects, dispatches replies, and reuses external Message-IDs for Sent copies.
JMAP message mapping and Message-ID propagation
src/modules/infrastructure/jmap/jmap-mail.mapper.ts, src/modules/infrastructure/jmap/jmap-mail.mapper.spec.ts
JMAP message creation generates or reuses Message-IDs, applies reply headers, and tests mailbox, email, draft, search, attachment, and threading mappings.
Provider threading errors and Sent persistence
src/modules/email/mail-provider.port.ts, src/modules/infrastructure/jmap/jmap-mail.provider.ts, src/modules/infrastructure/jmap/jmap-mail.provider.spec.ts
Providers accept Sent-copy Message-IDs, retrieve parent subjects, and throw MissingMessageIdError when a parent lacks a Message-ID.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EmailController
  participant EmailService
  participant JmapMailProvider
  participant JmapMapper
  EmailController->>EmailService: replyEmail(userEmail, parentId, dto, deliveryMode)
  EmailService->>JmapMailProvider: getThreadingHeaders(parentId)
  JmapMailProvider-->>EmailService: threading headers and parent subject
  EmailService->>JmapMailProvider: send reply
  JmapMailProvider->>JmapMapper: map message with Message-ID and reply headers
  JmapMapper-->>JmapMailProvider: JMAP email create payload
  JmapMailProvider-->>EmailService: delivery result
Loading

Suggested reviewers: apsantiso, jzunigax2

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly reflects the main change: refactoring message reply handling.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/reply-a-message

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@xabg2
xabg2 marked this pull request as ready for review July 21, 2026 11:59
@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/modules/email/email.service.ts (1)

209-263: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

dispatchExternal returns the SMTP Message-ID as the response id, not the created email's JMAP id.

saveToSent's result is awaited but discarded (Line 251), and the method returns { id: messageId } — the RFC5322 Message-ID header value from SMTP — instead of the JMAP entity id saveToSent actually created for the Sent copy. JMAP's Email.id and the Message-ID header are different identifier spaces (a JMAP id is what other endpoints such as GET /:id, PATCH /:id, DELETE /:id, GET threads/:id, and attachment download expect); EmailCreatedResponseDto.id is documented with a JMAP-shaped example ('Ma1f09b…').

Since dispatchExternal is now also the delivery path for the new POST :id/reply (EXTERNAL) endpoint, this contract mismatch affects the new reply feature too: a client replying externally gets back an unusable "id" for any follow-up call on that message.

🐛 Proposed fix
-    await this.mail.saveToSent(
+    const sentCopy = await this.mail.saveToSent(
       userEmail,
       {
         ...dto,
         textBody: dto.encryption ? packEnvelope(dto.encryption) : dto.textBody,
         htmlBody: undefined,
       },
       threading,
       messageId,
     );
 
-    return { id: messageId };
+    return { id: sentCopy.id };

Note: this will also require updating email.service.spec.ts assertions that currently expect the SMTP messageId as the returned id (e.g. the "plain external email"/"encrypted attachments" tests).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/email/email.service.ts` around lines 209 - 263, Update
dispatchExternal to capture the result returned by mail.saveToSent and return
its created JMAP email id instead of the SMTP messageId. Preserve messageId as
the value passed to saveToSent for threading and Sent-copy persistence, and
update the affected email.service.spec.ts expectations to assert the JMAP id.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/modules/email/email.service.ts`:
- Around line 209-263: Update dispatchExternal to capture the result returned by
mail.saveToSent and return its created JMAP email id instead of the SMTP
messageId. Preserve messageId as the value passed to saveToSent for threading
and Sent-copy persistence, and update the affected email.service.spec.ts
expectations to assert the JMAP id.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c470f2bc-bb70-425f-a56a-c05463e0457a

📥 Commits

Reviewing files that changed from the base of the PR and between 5eafc64 and 978f6c1.

📒 Files selected for processing (13)
  • src/modules/email/email.controller.spec.ts
  • src/modules/email/email.controller.ts
  • src/modules/email/email.dto.ts
  • src/modules/email/email.service.spec.ts
  • src/modules/email/email.service.ts
  • src/modules/email/email.types.ts
  • src/modules/email/mail-provider.port.ts
  • src/modules/email/threading.spec.ts
  • src/modules/email/threading.ts
  • src/modules/infrastructure/jmap/jmap-mail.mapper.spec.ts
  • src/modules/infrastructure/jmap/jmap-mail.mapper.ts
  • src/modules/infrastructure/jmap/jmap-mail.provider.spec.ts
  • src/modules/infrastructure/jmap/jmap-mail.provider.ts

@xabg2
xabg2 merged commit c182145 into master Jul 21, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants