Fix threaded draft email replies (#22175)
## Summary Fixes Gmail and Microsoft draft replies so workflow-created drafts stay attached to the existing provider thread. Fixes twentyhq/core-team-issues#2597. ## Root cause The email composer already resolved `threadExternalId` and `references` from `inReplyTo`, but `DraftEmailTool` only forwarded `inReplyTo` to the outbound draft service. Gmail therefore created a raw draft without `message.threadId`, which lets the draft appear as a standalone compose instead of an inline thread reply. For Microsoft, the draft path used Graph `createReply`, but parent lookup filtered on a URL-encoded `internetMessageId`. That can miss the parent message and fall back to creating a new draft message instead of a reply draft. ## Changes - Forward `threadExternalId` and `references` from `DraftEmailTool` to outbound draft creation. - Set Gmail draft `message.threadId` when `threadExternalId` is available. - Make Microsoft parent lookup use Graph request query builders with OData string escaping, so `createReply` is reached reliably. - Add targeted Jest coverage for the Draft Email tool, Gmail draft threading, and Microsoft reply-draft creation. ## Validation - `NX_DAEMON=false /Users/thomascolasdesfrancs/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node ../../node_modules/nx/dist/bin/nx.js jest twenty-server -- --runTestsByPath src/engine/core-modules/tool/tools/email-tool/__tests__/draft-email-tool.spec.ts src/modules/messaging/message-outbound-manager/drivers/gmail/services/__tests__/gmail-message-outbound.service.spec.ts src/modules/messaging/message-outbound-manager/drivers/microsoft/services/__tests__/microsoft-message-outbound.service.spec.ts --runInBand` - `NX_DAEMON=false /Users/thomascolasdesfrancs/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node ./node_modules/nx/dist/bin/nx.js lint:diff-with-main twenty-server` <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22175?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait <FelixMalfait@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
1076866820
commit
eedd838189
+18
-3
@@ -79,13 +79,20 @@ describe('DraftEmailTool', () => {
|
||||
});
|
||||
|
||||
it('creates the draft when the resolved account has the compose scope', async () => {
|
||||
mockComposeEmail.mockResolvedValue({
|
||||
success: true,
|
||||
data: buildComposedEmail({
|
||||
const composedEmail = {
|
||||
...buildComposedEmail({
|
||||
id: 'account-1',
|
||||
provider: ConnectedAccountProvider.GOOGLE,
|
||||
scopes: [GMAIL_COMPOSE_SCOPE],
|
||||
}),
|
||||
inReplyTo: '<parent@example.com>',
|
||||
threadExternalId: 'thread-external-id',
|
||||
references: ['<ancestor@example.com>', '<parent@example.com>'],
|
||||
};
|
||||
|
||||
mockComposeEmail.mockResolvedValue({
|
||||
success: true,
|
||||
data: composedEmail,
|
||||
});
|
||||
|
||||
const result = await tool.execute(baseInput, {
|
||||
@@ -94,5 +101,13 @@ describe('DraftEmailTool', () => {
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(mockCreateDraft).toHaveBeenCalledTimes(1);
|
||||
expect(mockCreateDraft).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
inReplyTo: composedEmail.inReplyTo,
|
||||
threadExternalId: composedEmail.threadExternalId,
|
||||
references: composedEmail.references,
|
||||
}),
|
||||
composedEmail.connectedAccount,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+2
@@ -114,6 +114,8 @@ export class DraftEmailTool implements Tool {
|
||||
html: data.sanitizedHtmlBody,
|
||||
attachments: data.attachments,
|
||||
inReplyTo: data.inReplyTo,
|
||||
threadExternalId: data.threadExternalId,
|
||||
references: data.references,
|
||||
},
|
||||
data.connectedAccount,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user