Skip to content

Conversation

JealousGx
Copy link
Contributor

Implement functionality to send messages using both IMAP and SMTP clients, allowing for message storage in the Sent folder.

Closes #14379

Copy link
Contributor

github-actions bot commented Sep 24, 2025

Welcome!

Hello there, congrats on your first PR! We're excited to have you contributing to this project.
By submitting your Pull Request, you acknowledge that you agree with the terms of our Contributor License Agreement.

Generated by 🚫 dangerJS against ac99c25

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Summary

This PR implements IMAP and SMTP client integration for message sending, addressing the feature request to copy workflow-sent emails to the "Sent" folder. The implementation adds support for storing sent messages in IMAP accounts' Sent folders.

Key Changes:

  • Added IMAP client integration alongside SMTP for IMAP_SMTP_CALDAV provider
  • Implemented parallel client initialization and proper message composition using MailComposer
  • Added automatic copying of sent messages to the Sent folder when available
  • Simplified Gmail message composition by removing multipart/alternative format
  • Removed HTML support from the interface, now only supporting plain text messages

Critical Issues Found:

  • Resource leak: SMTP client connections are not properly closed
  • Removed HTML support breaks backward compatibility for existing workflows
  • Missing error handling for IMAP operations could lead to partial failures
  • Potential inconsistency between message formats across different providers

Confidence Score: 2/5

  • This PR has critical resource management and backward compatibility issues
  • Score reflects resource leaks, breaking changes to HTML support, insufficient error handling for IMAP operations, and potential partial failure scenarios
  • messaging-send-message.service.ts requires attention for resource management, error handling, and backward compatibility

Important Files Changed

File Analysis

Filename        Score        Overview
packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-send-message.service.ts 2/5 integrates IMAP/SMTP for message sending with Sent folder storage, but removes HTML support and has resource leak issues

Sequence Diagram

sequenceDiagram
    participant W as Workflow
    participant MS as MessagingSendMessageService
    participant SC as SmtpClient
    participant IC as ImapClient
    participant GC as GmailClient
    participant MC as MicrosoftClient
    participant SF as SentFolder

    W->>MS: sendMessage(input, connectedAccount)
    
    alt Google Provider
        MS->>GC: getGmailClient()
        MS->>GC: userinfo.get()
        MS->>MS: buildHeaders(fromEmail, fromName)
        MS->>GC: users.messages.send(encodedMessage)
    else Microsoft Provider
        MS->>MC: getMicrosoftClient()
        MS->>MC: api('/me/messages').post(message)
        MS->>MC: api('/me/messages/{id}/send').post()
    else IMAP_SMTP_CALDAV Provider
        par Parallel Client Setup
            MS->>SC: getSmtpClient()
        and
            MS->>IC: getClient()
        end
        MS->>MS: new MailComposer(messageData)
        MS->>MS: compile().build() -> messageBuffer
        MS->>SC: sendMail(rawMessage)
        MS->>IC: list() -> folders
        MS->>SF: find(specialUse === '\\Sent')
        alt Sent Folder Found
            MS->>IC: append(sentFolder.path, messageBuffer)
        end
        MS->>IC: closeClient()
        Note over MS,SC: SMTP client not closed (resource leak)
    end
Loading

1 file reviewed, 4 comments

Edit Code Review Bot Settings | Greptile

Comment on lines 144 to 146
to: [sendMessageInput.to],
raw: messageBuffer,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

style: use object destructuring for better readability

Suggested change
to: [sendMessageInput.to],
raw: messageBuffer,
});
await smtpClient.sendMail({
from: connectedAccount.handle,
to: [sendMessageInput.to],
raw: messageBuffer,
});

Copy link
Contributor

Choose a reason for hiding this comment

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

@JealousGx
Could you please address this last comment?

Comment on lines 148 to 156
const sentFolder = await imapClient
.list()
.then((folders) =>
folders.find((folder) => folder.specialUse === '\\Sent'),
);

if (isDefined(sentFolder)) {
await imapClient.append(sentFolder.path, messageBuffer);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: if the Sent folder lookup fails (network error, permissions), the message is sent but not saved to Sent folder. add error handling to ensure message is either fully sent and saved, or operation fails completely

Suggested change
const sentFolder = await imapClient
.list()
.then((folders) =>
folders.find((folder) => folder.specialUse === '\\Sent'),
);
if (isDefined(sentFolder)) {
await imapClient.append(sentFolder.path, messageBuffer);
}
const sentFolder = await imapClient
.list()
.then((folders) =>
folders.find((folder) => folder.specialUse === '\\Sent'),
)
.catch((error) => {
throw new MessageImportDriverException(
`Failed to find Sent folder: ${error.message}`,
MessageImportDriverExceptionCode.CLIENT_NOT_AVAILABLE,
);
});
if (isDefined(sentFolder)) {
await imapClient.append(sentFolder.path, messageBuffer)
.catch((error) => {
throw new MessageImportDriverException(
`Failed to save message to Sent folder: ${error.message}`,
MessageImportDriverExceptionCode.CLIENT_NOT_AVAILABLE,
);
});
}

await imapClient.append(sentFolder.path, messageBuffer);
}

await this.imapClientProvider.closeClient(imapClient);
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: IMAP client is closed but SMTP client is not. this creates resource leaks for SMTP connections

Copy link
Contributor

github-actions bot commented Sep 24, 2025

🚀 Preview Environment Ready!

Your preview environment is available at: http://bore.pub:59914

This environment will automatically shut down when the PR is closed or after 5 hours.

raw: messageBuffer,
});

const sentFolder = await imapClient
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not efficient and also doesn't accounts for edge cases that ImapFindSentFolderService handles

I would suggest you to get the sent folder from messageFolders table instead, filter by isSentFolder: true

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually no need to query anything, connectedAccount already has messageChannel and messageFolders

you can do something like

const messageChannel = connectedAccount.messageChannels.find(
  (channel) => channel.handle === connectedAccount.handle
);

const sentFolder = messageChannel?.messageFolders.find(
  (messageFolder) => messageFolder.isSentFolder
);

await this.smtpClientProvider.getSmtpClient(connectedAccount);
const [smtpClient, imapClient] = await Promise.all([
this.smtpClientProvider.getSmtpClient(connectedAccount),
this.imapClientProvider.getClient(connectedAccount),
Copy link
Contributor

Choose a reason for hiding this comment

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

imapClientProvider.getClient() will throw an error if the user doesn't have an IMAP account connected, this assumes user will always have one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think there's much we can do on the backend in that case. For convenience, I've added logs when we can't get IMAP account.

.list()
.then((folders) =>
folders.find((folder) => folder.specialUse === '\\Sent'),
try {
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove this and call the client conditionally

isDefined(connectedAccount.connectionParameters?.IMAP)

@JealousGx JealousGx requested a review from neo773 September 26, 2025 07:39
@neo773
Copy link
Contributor

neo773 commented Sep 26, 2025

@JealousGx
Hi, could you please fix the failing tests in CI?

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.

Feature Request – Copy workflow‑sent emails to the “Sent” folder [for IMAP/SMTP connected accounts]
2 participants