From 9f3ebaaf22061eb306f613a104d65b9da6002fd4 Mon Sep 17 00:00:00 2001
From: neo773 <62795688+neo773@users.noreply.github.com>
Date: Tue, 30 Jun 2026 16:37:53 +0530
Subject: [PATCH] feat(messaging): sync draft emails and edit them in the
thread composer (#22178)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Stop excluding drafts from sync across all three providers (Gmail DRAFT
label, Microsoft/IMAP Drafts folder) and add an isDraft boolean field on
Message so drafts are queryable by the API and AI agents.
Drafts render in the thread with a Draft tag; clicking one opens the
existing reply composer pre-filled with the draft's recipients, subject
and body, and Send reuses the existing send-email flow.
---------
Co-authored-by: Félix Malfait
---
.../src/metadata/generated/schema.graphql | 2 +
.../src/metadata/generated/schema.ts | 4 +-
.../src/metadata/generated/types.ts | 6 +
.../src/generated-metadata/graphql.ts | 6 +-
.../twenty-front/src/generated/graphql.ts | 13 +-
.../emails/components/EmailComposerFields.tsx | 10 +-
.../emails/components/EmailThreadMessage.tsx | 116 +-
.../components/EmailThreadMessageLayout.tsx | 57 +
.../emails/components/EmailThreadPreview.tsx | 6 +-
.../emails/graphql/mutations/sendEmail.ts | 1 +
...ThreadMessagesOperationSignatureFactory.ts | 1 +
.../fragments/timelineThreadFragment.ts | 1 +
.../emails/hooks/useEmailComposerState.ts | 38 +-
.../emails/hooks/useReplyContext.ts | 24 +-
.../activities/emails/hooks/useSendEmail.ts | 18 +-
.../emails/types/EmailDraftPrefill.ts | 7 +
.../emails/types/EmailThreadMessage.ts | 1 +
.../utils/getEmailDraftPrefillFromMessage.ts | 22 +
.../components/EmailThreadComposer.tsx | 25 +-
.../EmailThreadIntermediaryMessages.tsx | 8 +-
.../components/EmailThreadWidget.tsx | 67 +-
.../getInitialAdvancedTextEditorContent.ts | 4 +-
.../2-18-upgrade-version-command.module.ts | 7 +-
...5000-add-message-is-draft-field.command.ts | 144 ++
.../messaging/dtos/timeline-thread.dto.ts | 3 +
.../services/timeline-messaging.service.ts | 1 +
...adata-related-entity-ids.util.spec.ts.snap | 1877 +++++++++--------
...ssage-standard-flat-field-metadata.util.ts | 20 +
...es-visibility-restrictions.service.spec.ts | 1 +
.../message.workspace-entity.ts | 1 +
..._FOLDER_MANAGER_ALWAYS_EXCLUDED_FOLDERS.ts | 1 -
...ould-create-folder-by-default.util.spec.ts | 2 +-
...g-gmail-excluded-system-labels.constant.ts | 7 +-
...ompute-gmail-exclude-search-filter.spec.ts | 2 +-
...arse-and-format-gmail-message.util.spec.ts | 28 +
.../parse-and-format-gmail-message.util.ts | 18 +-
.../services/imap-get-messages.service.ts | 3 +
.../services/imap-message-parser.service.ts | 11 +-
.../services/inbound-email-parser.service.ts | 1 +
.../microsoft-get-messages.service.spec.ts | 3 +
.../microsoft-get-messages.service.ts | 1 +
.../services/messaging-message.service.ts | 2 +
...d-enqueue-contact-creation.service.spec.ts | 38 +
...es-and-enqueue-contact-creation.service.ts | 3 +
.../utils/__mocks__/messages.ts | 3 +
.../__tests__/filter-emails.util.spec.ts | 10 +
.../email-group-message-outbound.service.ts | 7 +
.../gmail-message-outbound.service.ts | 76 +-
.../imap-smtp-message-outbound.service.ts | 55 +-
.../microsoft-message-outbound.service.ts | 30 +-
.../dtos/send-email-output.dto.ts | 3 +
.../dtos/send-email.input.ts | 3 +
.../message-outbound-driver.interface.ts | 6 +
.../messaging-send-manager.module.ts | 5 +
.../resolvers/send-email.resolver.ts | 68 +-
.../services/messaging-draft-send.service.ts | 171 ++
.../messaging-message-outbound.service.ts | 39 +
.../services/send-email.service.ts | 66 +-
.../utils/format-sent-message.util.ts | 1 +
.../constants/standard-object.constant.ts | 3 +
60 files changed, 2057 insertions(+), 1100 deletions(-)
create mode 100644 packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessageLayout.tsx
create mode 100644 packages/twenty-front/src/modules/activities/emails/types/EmailDraftPrefill.ts
create mode 100644 packages/twenty-front/src/modules/activities/emails/utils/getEmailDraftPrefillFromMessage.ts
create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-workspace-command-1810000005000-add-message-is-draft-field.command.ts
create mode 100644 packages/twenty-server/src/modules/messaging/message-outbound-manager/services/messaging-draft-send.service.ts
diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
index 4d8d52de11..7db61ae21f 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
+++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
@@ -2789,6 +2789,7 @@ type DuplicatedDashboard {
type SendEmailOutput {
success: Boolean!
error: String
+ messageThreadId: String
}
type Analytics {
@@ -4609,6 +4610,7 @@ input SendEmailInput {
subject: String!
body: String!
inReplyTo: String
+ draftMessageId: String
files: [SendEmailAttachmentInput!]
}
diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts
index c75e835726..61fbf816a3 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts
+++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts
@@ -2453,6 +2453,7 @@ export interface DuplicatedDashboard {
export interface SendEmailOutput {
success: Scalars['Boolean']
error?: Scalars['String']
+ messageThreadId?: Scalars['String']
__typename: 'SendEmailOutput'
}
@@ -5611,6 +5612,7 @@ export interface DuplicatedDashboardGenqlSelection{
export interface SendEmailOutputGenqlSelection{
success?: boolean | number
error?: boolean | number
+ messageThreadId?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
@@ -6590,7 +6592,7 @@ export interface EditSsoInput {id: Scalars['UUID'],status: SSOIdentityProviderSt
export interface CreateCalendarEventInput {connectedAccountId: Scalars['String'],title: Scalars['String'],description?: (Scalars['String'] | null),location?: (Scalars['String'] | null),startsAt: Scalars['String'],endsAt: Scalars['String'],isFullDay?: (Scalars['Boolean'] | null),timeZone?: (Scalars['String'] | null),attendees?: (Scalars['String'] | null),sendInvitations?: (Scalars['Boolean'] | null),addConferencing?: (Scalars['Boolean'] | null)}
-export interface SendEmailInput {connectedAccountId: Scalars['String'],to: Scalars['String'],cc?: (Scalars['String'] | null),bcc?: (Scalars['String'] | null),subject: Scalars['String'],body: Scalars['String'],inReplyTo?: (Scalars['String'] | null),files?: (SendEmailAttachmentInput[] | null)}
+export interface SendEmailInput {connectedAccountId: Scalars['String'],to: Scalars['String'],cc?: (Scalars['String'] | null),bcc?: (Scalars['String'] | null),subject: Scalars['String'],body: Scalars['String'],inReplyTo?: (Scalars['String'] | null),draftMessageId?: (Scalars['String'] | null),files?: (SendEmailAttachmentInput[] | null)}
export interface SendEmailAttachmentInput {id: Scalars['String'],name: Scalars['String']}
diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts
index ac68e81386..a3d06304bc 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/types.ts
+++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts
@@ -5549,6 +5549,9 @@ export default {
"error": [
1
],
+ "messageThreadId": [
+ 1
+ ],
"__typename": [
1
]
@@ -11702,6 +11705,9 @@ export default {
"inReplyTo": [
1
],
+ "draftMessageId": [
+ 1
+ ],
"files": [
491
],
diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts
index 84b5ea4487..cbff9fd2fb 100644
--- a/packages/twenty-front/src/generated-metadata/graphql.ts
+++ b/packages/twenty-front/src/generated-metadata/graphql.ts
@@ -5004,6 +5004,7 @@ export type SendEmailInput = {
body: Scalars['String']['input'];
cc?: InputMaybe;
connectedAccountId: Scalars['String']['input'];
+ draftMessageId?: InputMaybe;
files?: InputMaybe>;
inReplyTo?: InputMaybe;
subject: Scalars['String']['input'];
@@ -5013,6 +5014,7 @@ export type SendEmailInput = {
export type SendEmailOutput = {
__typename?: 'SendEmailOutput';
error?: Maybe;
+ messageThreadId?: Maybe;
success: Scalars['Boolean']['output'];
};
@@ -6379,7 +6381,7 @@ export type SendEmailMutationVariables = Exact<{
}>;
-export type SendEmailMutation = { __typename?: 'Mutation', sendEmail: { __typename?: 'SendEmailOutput', success: boolean, error?: string | null } };
+export type SendEmailMutation = { __typename?: 'Mutation', sendEmail: { __typename?: 'SendEmailOutput', success: boolean, error?: string | null, messageThreadId?: string | null } };
export type SendMessageCampaignMutationVariables = Exact<{
input: SendMessageCampaignInput;
@@ -8719,7 +8721,7 @@ export const ViewGroupFragmentFragmentDoc = {"kind":"Document","definitions":[{"
export const ViewFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"View"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"objectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"isCompact"}},{"kind":"Field","name":{"kind":"Name","value":"openRecordIn"}},{"kind":"Field","name":{"kind":"Name","value":"kanbanAggregateOperation"}},{"kind":"Field","name":{"kind":"Name","value":"kanbanAggregateOperationFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"mainGroupByFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"shouldHideEmptyGroups"}},{"kind":"Field","name":{"kind":"Name","value":"kanbanColumnWidth"}},{"kind":"Field","name":{"kind":"Name","value":"anyFieldFilterValue"}},{"kind":"Field","name":{"kind":"Name","value":"calendarFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"calendarLayout"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"createdByUserWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"viewFields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewFieldFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"viewFieldGroups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewFieldGroupFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"viewFilters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewFilterFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"viewFilterGroups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewFilterGroupFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"viewSorts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewSortFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"viewGroups"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewGroupFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewFieldFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ViewField"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"isVisible"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"size"}},{"kind":"Field","name":{"kind":"Name","value":"aggregateOperation"}},{"kind":"Field","name":{"kind":"Name","value":"viewFieldGroupId"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"deletedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewFieldGroupFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ViewFieldGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"isVisible"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"deletedAt"}},{"kind":"Field","name":{"kind":"Name","value":"viewFields"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ViewFieldFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewFilterFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ViewFilter"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"operand"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"viewFilterGroupId"}},{"kind":"Field","name":{"kind":"Name","value":"positionInViewFilterGroup"}},{"kind":"Field","name":{"kind":"Name","value":"subFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"relationTargetFieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"deletedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewFilterGroupFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ViewFilterGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"parentViewFilterGroupId"}},{"kind":"Field","name":{"kind":"Name","value":"logicalOperator"}},{"kind":"Field","name":{"kind":"Name","value":"positionInViewFilterGroup"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewSortFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ViewSort"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"fieldMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"direction"}},{"kind":"Field","name":{"kind":"Name","value":"subFieldName"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"deletedAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewGroupFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ViewGroup"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isVisible"}},{"kind":"Field","name":{"kind":"Name","value":"fieldValue"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"deletedAt"}}]}}]} as unknown as DocumentNode;
export const PreviewMessageCampaignAudienceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PreviewMessageCampaignAudience"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PreviewMessageCampaignAudienceInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"previewMessageCampaignAudience"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalMembers"}},{"kind":"Field","name":{"kind":"Name","value":"withoutEmail"}},{"kind":"Field","name":{"kind":"Name","value":"duplicateEmails"}},{"kind":"Field","name":{"kind":"Name","value":"globallyUnsubscribed"}},{"kind":"Field","name":{"kind":"Name","value":"topicUnsubscribed"}},{"kind":"Field","name":{"kind":"Name","value":"sendable"}}]}}]}}]} as unknown as DocumentNode;
export const UnsubscribeTopicsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"UnsubscribeTopics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unsubscribeTopics"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}}]}}]}}]} as unknown as DocumentNode;
-export const SendEmailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SendEmail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SendEmailInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendEmail"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]} as unknown as DocumentNode;
+export const SendEmailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SendEmail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SendEmailInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendEmail"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"messageThreadId"}}]}}]}}]} as unknown as DocumentNode;
export const SendMessageCampaignDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SendMessageCampaign"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SendMessageCampaignInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendMessageCampaign"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"campaignId"}},{"kind":"Field","name":{"kind":"Name","value":"queuedCount"}},{"kind":"Field","name":{"kind":"Name","value":"skipped"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"noEmail"}},{"kind":"Field","name":{"kind":"Name","value":"deduped"}},{"kind":"Field","name":{"kind":"Name","value":"overCap"}}]}}]}}]}}]} as unknown as DocumentNode;
export const ActivateSkillDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ActivateSkill"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activateSkill"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SkillFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SkillFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Skill"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"isActive"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode;
export const ArchiveChatThreadDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ArchiveChatThread"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archiveChatThread"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"deletedAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode;
diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts
index fd5e4e1d51..7d72f99720 100644
--- a/packages/twenty-front/src/generated/graphql.ts
+++ b/packages/twenty-front/src/generated/graphql.ts
@@ -545,6 +545,7 @@ export type TimelineThread = {
firstParticipant: TimelineThreadParticipant;
id: Scalars['UUID']['output'];
lastMessageBody: Scalars['String']['output'];
+ lastMessageIsDraft: Scalars['Boolean']['output'];
lastMessageReceivedAt: Scalars['DateTime']['output'];
lastTwoParticipants: Array;
numberOfMessagesInThread: Scalars['Float']['output'];
@@ -708,9 +709,9 @@ export type GetTimelineCalendarEventsFromObjectRecordQuery = { __typename?: 'Que
export type ParticipantFragmentFragment = { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string };
-export type TimelineThreadFragmentFragment = { __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> };
+export type TimelineThreadFragmentFragment = { __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, lastMessageIsDraft: boolean, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> };
-export type TimelineThreadsWithTotalFragmentFragment = { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, relatedPersonIds: Array, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> };
+export type TimelineThreadsWithTotalFragmentFragment = { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, relatedPersonIds: Array, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, lastMessageIsDraft: boolean, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> };
export type GetTimelineThreadsFromObjectRecordQueryVariables = Exact<{
objectNameSingular: Scalars['String']['input'];
@@ -720,7 +721,7 @@ export type GetTimelineThreadsFromObjectRecordQueryVariables = Exact<{
}>;
-export type GetTimelineThreadsFromObjectRecordQuery = { __typename?: 'Query', getTimelineThreadsFromObjectRecord: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, relatedPersonIds: Array, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } };
+export type GetTimelineThreadsFromObjectRecordQuery = { __typename?: 'Query', getTimelineThreadsFromObjectRecord: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, relatedPersonIds: Array, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, lastMessageIsDraft: boolean, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } };
export type SearchQueryVariables = Exact<{
searchInput: Scalars['String']['input'];
@@ -873,11 +874,11 @@ export const TimelineCalendarEventParticipantFragmentFragmentDoc = {"kind":"Docu
export const TimelineCalendarEventFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"startsAt"}},{"kind":"Field","name":{"kind":"Name","value":"endsAt"}},{"kind":"Field","name":{"kind":"Name","value":"isFullDay"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"participants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}}]} as unknown as DocumentNode;
export const TimelineCalendarEventsWithTotalFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfCalendarEvents"}},{"kind":"Field","name":{"kind":"Name","value":"relatedPersonIds"}},{"kind":"Field","name":{"kind":"Name","value":"timelineCalendarEvents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"startsAt"}},{"kind":"Field","name":{"kind":"Name","value":"endsAt"}},{"kind":"Field","name":{"kind":"Name","value":"isFullDay"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"participants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"}}]}}]}}]} as unknown as DocumentNode;
export const ParticipantFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}}]} as unknown as DocumentNode;
-export const TimelineThreadFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThread"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"read"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"firstParticipant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastTwoParticipants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageReceivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageBody"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfMessagesInThread"}},{"kind":"Field","name":{"kind":"Name","value":"participantCount"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}}]} as unknown as DocumentNode;
-export const TimelineThreadsWithTotalFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfThreads"}},{"kind":"Field","name":{"kind":"Name","value":"relatedPersonIds"}},{"kind":"Field","name":{"kind":"Name","value":"timelineThreads"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThread"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"read"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"firstParticipant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastTwoParticipants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageReceivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageBody"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfMessagesInThread"}},{"kind":"Field","name":{"kind":"Name","value":"participantCount"}}]}}]} as unknown as DocumentNode;
+export const TimelineThreadFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThread"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"read"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"firstParticipant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastTwoParticipants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageReceivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageBody"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfMessagesInThread"}},{"kind":"Field","name":{"kind":"Name","value":"participantCount"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageIsDraft"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}}]} as unknown as DocumentNode;
+export const TimelineThreadsWithTotalFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfThreads"}},{"kind":"Field","name":{"kind":"Name","value":"relatedPersonIds"}},{"kind":"Field","name":{"kind":"Name","value":"timelineThreads"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThread"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"read"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"firstParticipant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastTwoParticipants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageReceivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageBody"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfMessagesInThread"}},{"kind":"Field","name":{"kind":"Name","value":"participantCount"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageIsDraft"}}]}}]} as unknown as DocumentNode;
export const WorkflowDiffFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkflowDiffFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"WorkflowVersionStepChanges"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"triggerDiff"}},{"kind":"Field","name":{"kind":"Name","value":"stepsDiff"}}]}}]} as unknown as DocumentNode;
export const GetTimelineCalendarEventsFromObjectRecordDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTimelineCalendarEventsFromObjectRecord"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"objectNameSingular"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"recordId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getTimelineCalendarEventsFromObjectRecord"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"objectNameSingular"},"value":{"kind":"Variable","name":{"kind":"Name","value":"objectNameSingular"}}},{"kind":"Argument","name":{"kind":"Name","value":"recordId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"recordId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotalFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"location"}},{"kind":"Field","name":{"kind":"Name","value":"startsAt"}},{"kind":"Field","name":{"kind":"Name","value":"endsAt"}},{"kind":"Field","name":{"kind":"Name","value":"isFullDay"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"participants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventParticipantFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineCalendarEventsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfCalendarEvents"}},{"kind":"Field","name":{"kind":"Name","value":"relatedPersonIds"}},{"kind":"Field","name":{"kind":"Name","value":"timelineCalendarEvents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineCalendarEventFragment"}}]}}]}}]} as unknown as DocumentNode;
-export const GetTimelineThreadsFromObjectRecordDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTimelineThreadsFromObjectRecord"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"objectNameSingular"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"recordId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getTimelineThreadsFromObjectRecord"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"objectNameSingular"},"value":{"kind":"Variable","name":{"kind":"Name","value":"objectNameSingular"}}},{"kind":"Argument","name":{"kind":"Name","value":"recordId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"recordId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThread"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"read"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"firstParticipant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastTwoParticipants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageReceivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageBody"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfMessagesInThread"}},{"kind":"Field","name":{"kind":"Name","value":"participantCount"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfThreads"}},{"kind":"Field","name":{"kind":"Name","value":"relatedPersonIds"}},{"kind":"Field","name":{"kind":"Name","value":"timelineThreads"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadFragment"}}]}}]}}]} as unknown as DocumentNode;
+export const GetTimelineThreadsFromObjectRecordDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetTimelineThreadsFromObjectRecord"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"objectNameSingular"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"recordId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"page"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getTimelineThreadsFromObjectRecord"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"objectNameSingular"},"value":{"kind":"Variable","name":{"kind":"Name","value":"objectNameSingular"}}},{"kind":"Argument","name":{"kind":"Name","value":"recordId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"recordId"}}},{"kind":"Argument","name":{"kind":"Name","value":"page"},"value":{"kind":"Variable","name":{"kind":"Name","value":"page"}}},{"kind":"Argument","name":{"kind":"Name","value":"pageSize"},"value":{"kind":"Variable","name":{"kind":"Name","value":"pageSize"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ParticipantFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadParticipant"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"personId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceMemberId"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThread"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"read"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"firstParticipant"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastTwoParticipants"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ParticipantFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageReceivedAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageBody"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"numberOfMessagesInThread"}},{"kind":"Field","name":{"kind":"Name","value":"participantCount"}},{"kind":"Field","name":{"kind":"Name","value":"lastMessageIsDraft"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TimelineThreadsWithTotalFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TimelineThreadsWithTotal"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalNumberOfThreads"}},{"kind":"Field","name":{"kind":"Name","value":"relatedPersonIds"}},{"kind":"Field","name":{"kind":"Name","value":"timelineThreads"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"TimelineThreadFragment"}}]}}]}}]} as unknown as DocumentNode;
export const SearchDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Search"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchInput"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"after"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"excludedObjectNameSingulars"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"includedObjectNameSingulars"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ObjectRecordFilterInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"search"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"searchInput"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchInput"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"after"}}},{"kind":"Argument","name":{"kind":"Name","value":"excludedObjectNameSingulars"},"value":{"kind":"Variable","name":{"kind":"Name","value":"excludedObjectNameSingulars"}}},{"kind":"Argument","name":{"kind":"Name","value":"includedObjectNameSingulars"},"value":{"kind":"Variable","name":{"kind":"Name","value":"includedObjectNameSingulars"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"recordId"}},{"kind":"Field","name":{"kind":"Name","value":"objectNameSingular"}},{"kind":"Field","name":{"kind":"Name","value":"objectLabelSingular"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"imageUrl"}},{"kind":"Field","name":{"kind":"Name","value":"tsRankCD"}},{"kind":"Field","name":{"kind":"Name","value":"tsRank"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pageInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hasNextPage"}},{"kind":"Field","name":{"kind":"Name","value":"endCursor"}}]}}]}}]}}]} as unknown as DocumentNode;
export const ActivateWorkflowVersionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ActivateWorkflowVersion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workflowVersionId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activateWorkflowVersion"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workflowVersionId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workflowVersionId"}}}]}]}}]} as unknown as DocumentNode;
export const ComputeStepOutputSchemaDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ComputeStepOutputSchema"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ComputeStepOutputSchemaInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"computeStepOutputSchema"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode;
diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailComposerFields.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailComposerFields.tsx
index 8ae0cbb18c..52c91f1b82 100644
--- a/packages/twenty-front/src/modules/activities/emails/components/EmailComposerFields.tsx
+++ b/packages/twenty-front/src/modules/activities/emails/components/EmailComposerFields.tsx
@@ -73,7 +73,7 @@ export const EmailComposerFields = ({
@@ -87,13 +87,13 @@ export const EmailComposerFields = ({
<>
@@ -101,12 +101,12 @@ export const EmailComposerFields = ({
)}
`
- border-bottom: ${({ hideBottomBorder }) =>
- hideBottomBorder
- ? 'none'
- : `1px solid ${themeCssVariables.border.color.light}`};
- display: flex;
- flex-direction: column;
- padding: ${themeCssVariables.spacing[4]} ${themeCssVariables.spacing[0]};
-`;
-
-const StyledThreadMessageHeader = styled.div`
- cursor: pointer;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- margin-bottom: ${themeCssVariables.spacing[2]};
- padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[2]};
-`;
-
-const StyledThreadMessageBody = styled.div`
- padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[2]};
-`;
-
type EmailThreadMessageProps = {
- body: string;
- sentAt: string;
- sender: EmailThreadMessageParticipant;
- participants: EmailThreadMessageParticipant[];
+ message: EmailThreadMessageWithSender;
isExpanded?: boolean;
hideBottomBorder?: boolean;
+ onDraftClick: (message: EmailThreadMessageWithSender) => void;
};
export const EmailThreadMessage = ({
- body,
- sentAt,
- sender,
- participants,
+ message,
isExpanded = false,
hideBottomBorder = false,
+ onDraftClick,
}: EmailThreadMessageProps) => {
const [isOpen, setIsOpen] = useState(isExpanded);
- const receivers = participants.filter(
+ const receivers = message.messageParticipants.filter(
(participant) => participant.role !== MessageParticipantRole.FROM,
);
- if (!isDefined(sender) || receivers.length === 0) {
+ if (
+ !isDefined(message.sender) ||
+ (!message.isDraft && receivers.length === 0)
+ ) {
return null;
}
+ const { isDraft } = message;
+
const isRestricted =
- body === FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED;
+ message.text === FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED;
+
+ const handleRowClick = () => {
+ if (isRestricted) {
+ return;
+ }
+
+ if (isDraft) {
+ onDraftClick(message);
+
+ return;
+ }
+
+ if (!isOpen) {
+ setIsOpen(true);
+ }
+ };
+
+ const handleHeaderClick = () => {
+ if (!isDraft && isOpen) {
+ setIsOpen(false);
+ }
+ };
return (
- !isOpen && setIsOpen(true)}
- style={{ cursor: isOpen || isRestricted ? 'auto' : 'pointer' }}
- >
- isOpen && setIsOpen(false)}>
-
- {isOpen && }
-
-
- {isRestricted ? (
-
+
- ) : isOpen ? (
-
- ) : (
-
- )}
-
-
+ {!isDraft && isOpen && (
+
+ )}
+ >
+ }
+ >
+ {isRestricted ? (
+
+ ) : isDraft || !isOpen ? (
+
+ ) : (
+
+ )}
+
);
};
diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessageLayout.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessageLayout.tsx
new file mode 100644
index 0000000000..e9a70aa630
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadMessageLayout.tsx
@@ -0,0 +1,57 @@
+import { styled } from '@linaria/react';
+import { type ReactNode } from 'react';
+import { themeCssVariables } from 'twenty-ui/theme-constants';
+
+const StyledThreadMessage = styled.div<{ hideBottomBorder?: boolean }>`
+ border-bottom: ${({ hideBottomBorder }) =>
+ hideBottomBorder
+ ? 'none'
+ : `1px solid ${themeCssVariables.border.color.light}`};
+ display: flex;
+ flex-direction: column;
+ padding: ${themeCssVariables.spacing[4]} ${themeCssVariables.spacing[0]};
+`;
+
+const StyledHeader = styled.div<{ isClickable?: boolean }>`
+ cursor: ${({ isClickable }) => (isClickable ? 'pointer' : 'auto')};
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ margin-bottom: ${themeCssVariables.spacing[2]};
+ padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[2]};
+`;
+
+const StyledBody = styled.div`
+ padding: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[2]};
+`;
+
+type EmailThreadMessageLayoutProps = {
+ header: ReactNode;
+ children: ReactNode;
+ hideBottomBorder?: boolean;
+ isRowClickable?: boolean;
+ isHeaderClickable?: boolean;
+ onRowClick?: () => void;
+ onHeaderClick?: () => void;
+};
+
+export const EmailThreadMessageLayout = ({
+ header,
+ children,
+ hideBottomBorder = false,
+ isRowClickable = false,
+ isHeaderClickable = false,
+ onRowClick,
+ onHeaderClick,
+}: EmailThreadMessageLayoutProps) => (
+
+
+ {header}
+
+ {children}
+
+);
diff --git a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx
index 42efe7d4e0..3bd7cadf9e 100644
--- a/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx
+++ b/packages/twenty-front/src/modules/activities/emails/components/EmailThreadPreview.tsx
@@ -5,9 +5,10 @@ import { EmailThreadNotShared } from '@/activities/emails/components/EmailThread
import { useOpenRecordInSidePanel } from '@/side-panel/hooks/useOpenRecordInSidePanel';
import { useContext } from 'react';
+import { t } from '@lingui/core/macro';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
-import { Avatar } from 'twenty-ui/data-display';
+import { Avatar, Tag } from 'twenty-ui/data-display';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import {
MessageChannelVisibility,
@@ -173,6 +174,9 @@ export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
)}
{visibility === MessageChannelVisibility.SHARE_EVERYTHING && (
<>
+ {thread.lastMessageIsDraft && (
+
+ )}
{thread.subject}
{thread.lastMessageBody}
>
diff --git a/packages/twenty-front/src/modules/activities/emails/graphql/mutations/sendEmail.ts b/packages/twenty-front/src/modules/activities/emails/graphql/mutations/sendEmail.ts
index 14a0f15178..e7acb3f7e4 100644
--- a/packages/twenty-front/src/modules/activities/emails/graphql/mutations/sendEmail.ts
+++ b/packages/twenty-front/src/modules/activities/emails/graphql/mutations/sendEmail.ts
@@ -5,6 +5,7 @@ export const SEND_EMAIL = gql`
sendEmail(input: $input) {
success
error
+ messageThreadId
}
}
`;
diff --git a/packages/twenty-front/src/modules/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory.ts b/packages/twenty-front/src/modules/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory.ts
index b023208c15..f4f839ebed 100644
--- a/packages/twenty-front/src/modules/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory.ts
+++ b/packages/twenty-front/src/modules/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory.ts
@@ -31,6 +31,7 @@ export const fetchAllThreadMessagesOperationSignatureFactory: RecordGqlOperation
subject: true,
text: true,
receivedAt: true,
+ isDraft: true,
messageThread: {
id: true,
},
diff --git a/packages/twenty-front/src/modules/activities/emails/graphql/queries/fragments/timelineThreadFragment.ts b/packages/twenty-front/src/modules/activities/emails/graphql/queries/fragments/timelineThreadFragment.ts
index 7d8f8ab9c3..a6d627d01d 100644
--- a/packages/twenty-front/src/modules/activities/emails/graphql/queries/fragments/timelineThreadFragment.ts
+++ b/packages/twenty-front/src/modules/activities/emails/graphql/queries/fragments/timelineThreadFragment.ts
@@ -18,6 +18,7 @@ export const timelineThreadFragment = gql`
subject
numberOfMessagesInThread
participantCount
+ lastMessageIsDraft
}
${participantFragment}
`;
diff --git a/packages/twenty-front/src/modules/activities/emails/hooks/useEmailComposerState.ts b/packages/twenty-front/src/modules/activities/emails/hooks/useEmailComposerState.ts
index 0df7aed464..cebac50e67 100644
--- a/packages/twenty-front/src/modules/activities/emails/hooks/useEmailComposerState.ts
+++ b/packages/twenty-front/src/modules/activities/emails/hooks/useEmailComposerState.ts
@@ -3,13 +3,15 @@ import { MAX_EMAIL_RECIPIENTS } from 'twenty-shared/constants';
import { type EmailAttachment } from 'twenty-shared/types';
import { useSendEmail } from '@/activities/emails/hooks/useSendEmail';
+import { type EmailDraftPrefill } from '@/activities/emails/types/EmailDraftPrefill';
type UseEmailComposerStateArgs = {
connectedAccountId: string;
+ draftPrefill?: EmailDraftPrefill | null;
defaultTo?: string;
defaultSubject?: string;
defaultInReplyTo?: string;
- onSent?: () => void;
+ onSent?: (messageThreadId: string | null) => void;
};
const countRecipients = (csv: string): number =>
@@ -20,20 +22,29 @@ const countRecipients = (csv: string): number =>
export const useEmailComposerState = ({
connectedAccountId: initialConnectedAccountId,
+ draftPrefill,
defaultTo = '',
defaultSubject = '',
defaultInReplyTo,
onSent,
}: UseEmailComposerStateArgs) => {
+ const initialTo = draftPrefill?.to ?? defaultTo;
+ const initialCc = draftPrefill?.cc ?? '';
+ const initialBcc = draftPrefill?.bcc ?? '';
+ const initialSubject = draftPrefill?.subject ?? defaultSubject;
+ const initialBody = draftPrefill?.body ?? '';
+
const [connectedAccountId, setConnectedAccountId] = useState(
initialConnectedAccountId,
);
- const [to, setTo] = useState(defaultTo);
- const [cc, setCc] = useState('');
- const [bcc, setBcc] = useState('');
- const [subject, setSubject] = useState(defaultSubject);
- const [body, setBody] = useState('');
- const [showCcBcc, setShowCcBcc] = useState(false);
+ const [to, setTo] = useState(initialTo);
+ const [cc, setCc] = useState(initialCc);
+ const [bcc, setBcc] = useState(initialBcc);
+ const [subject, setSubject] = useState(initialSubject);
+ const [body, setBody] = useState(initialBody);
+ const [showCcBcc, setShowCcBcc] = useState(
+ initialCc.length > 0 || initialBcc.length > 0,
+ );
const [files, setFiles] = useState([]);
const { sendEmail, loading } = useSendEmail();
@@ -60,7 +71,7 @@ export const useEmailComposerState = ({
const trimmedCc = cc.trim();
const trimmedBcc = bcc.trim();
- const success = await sendEmail({
+ const { success, messageThreadId } = await sendEmail({
connectedAccountId,
to: trimmedTo,
cc: trimmedCc || undefined,
@@ -68,11 +79,12 @@ export const useEmailComposerState = ({
subject,
body,
inReplyTo: defaultInReplyTo,
+ draftMessageId: draftPrefill?.messageId,
files: files.length > 0 ? files : undefined,
});
if (success) {
- onSent?.();
+ onSent?.(messageThreadId);
}
}, [
connectedAccountId,
@@ -82,6 +94,7 @@ export const useEmailComposerState = ({
subject,
body,
defaultInReplyTo,
+ draftPrefill?.messageId,
files,
sendEmail,
onSent,
@@ -108,8 +121,11 @@ export const useEmailComposerState = ({
handleSend,
loading,
canSend,
- defaultTo,
- defaultSubject,
+ initialTo,
+ initialCc,
+ initialBcc,
+ initialSubject,
+ initialBody,
recipientCount,
exceedsRecipientLimit,
maxRecipients: MAX_EMAIL_RECIPIENTS,
diff --git a/packages/twenty-front/src/modules/activities/emails/hooks/useReplyContext.ts b/packages/twenty-front/src/modules/activities/emails/hooks/useReplyContext.ts
index 6a8f8acb67..a54535b6aa 100644
--- a/packages/twenty-front/src/modules/activities/emails/hooks/useReplyContext.ts
+++ b/packages/twenty-front/src/modules/activities/emails/hooks/useReplyContext.ts
@@ -32,15 +32,27 @@ export const useReplyContext = (
return null;
}
- const lastMessage = messages[messages.length - 1];
+ const sentMessages = messages.filter((message) => !message.isDraft);
+ const lastSentMessage = sentMessages[sentMessages.length - 1];
- if (!isDefined(lastMessage)) {
- return null;
+ if (!isDefined(lastSentMessage)) {
+ if (messages.length === 0) {
+ return null;
+ }
+
+ return {
+ loading: false,
+ to: '',
+ subject: '',
+ inReplyTo: '',
+ connectedAccountId,
+ connectedAccountProvider,
+ };
}
- const senderHandle = lastMessage.sender?.handle ?? '';
+ const senderHandle = lastSentMessage.sender?.handle ?? '';
- const rawSubject = lastMessage.subject ?? '';
+ const rawSubject = lastSentMessage.subject ?? '';
const subject = rawSubject.startsWith('Re: ')
? rawSubject
: `Re: ${rawSubject}`;
@@ -49,7 +61,7 @@ export const useReplyContext = (
loading: false,
to: senderHandle,
subject,
- inReplyTo: lastMessage.headerMessageId ?? '',
+ inReplyTo: lastSentMessage.headerMessageId ?? '',
connectedAccountId,
connectedAccountProvider,
};
diff --git a/packages/twenty-front/src/modules/activities/emails/hooks/useSendEmail.ts b/packages/twenty-front/src/modules/activities/emails/hooks/useSendEmail.ts
index e7d8f66435..fb272d52fb 100644
--- a/packages/twenty-front/src/modules/activities/emails/hooks/useSendEmail.ts
+++ b/packages/twenty-front/src/modules/activities/emails/hooks/useSendEmail.ts
@@ -12,6 +12,11 @@ import {
type SendEmailMutationVariables,
} from '~/generated-metadata/graphql';
+type SendEmailResult = {
+ success: boolean;
+ messageThreadId: string | null;
+};
+
type SendEmailParams = {
connectedAccountId: string;
to: string;
@@ -20,6 +25,7 @@ type SendEmailParams = {
subject: string;
body: string;
inReplyTo?: string;
+ draftMessageId?: string;
files?: EmailAttachment[];
};
@@ -34,7 +40,7 @@ export const useSendEmail = () => {
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
const sendEmail = useCallback(
- async (params: SendEmailParams): Promise => {
+ async (params: SendEmailParams): Promise => {
try {
const result = await sendEmailMutation({
variables: {
@@ -46,6 +52,7 @@ export const useSendEmail = () => {
subject: params.subject,
body: params.body,
inReplyTo: params.inReplyTo,
+ draftMessageId: params.draftMessageId,
files: params.files,
},
},
@@ -65,20 +72,23 @@ export const useSendEmail = () => {
],
});
- return true;
+ return {
+ success: true,
+ messageThreadId: result.data.sendEmail.messageThreadId ?? null,
+ };
}
enqueueErrorSnackBar({
message: result.data?.sendEmail.error ?? t`Failed to send email`,
});
- return false;
+ return { success: false, messageThreadId: null };
} catch {
enqueueErrorSnackBar({
message: t`Failed to send email`,
});
- return false;
+ return { success: false, messageThreadId: null };
}
},
[
diff --git a/packages/twenty-front/src/modules/activities/emails/types/EmailDraftPrefill.ts b/packages/twenty-front/src/modules/activities/emails/types/EmailDraftPrefill.ts
new file mode 100644
index 0000000000..dd1ad2d6c2
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/emails/types/EmailDraftPrefill.ts
@@ -0,0 +1,7 @@
+import { type EmailRecipients } from 'twenty-shared/workflow';
+
+export type EmailDraftPrefill = EmailRecipients & {
+ messageId: string;
+ subject: string;
+ body: string;
+};
diff --git a/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessage.ts b/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessage.ts
index cd67340d67..07a910caf7 100644
--- a/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessage.ts
+++ b/packages/twenty-front/src/modules/activities/emails/types/EmailThreadMessage.ts
@@ -10,5 +10,6 @@ export type EmailThreadMessage = {
messageThreadId: string;
messageParticipants: EmailThreadMessageParticipant[];
messageThread: MessageThread;
+ isDraft: boolean;
__typename: 'EmailThreadMessage';
};
diff --git a/packages/twenty-front/src/modules/activities/emails/utils/getEmailDraftPrefillFromMessage.ts b/packages/twenty-front/src/modules/activities/emails/utils/getEmailDraftPrefillFromMessage.ts
new file mode 100644
index 0000000000..e6960c8157
--- /dev/null
+++ b/packages/twenty-front/src/modules/activities/emails/utils/getEmailDraftPrefillFromMessage.ts
@@ -0,0 +1,22 @@
+import { type EmailDraftPrefill } from '@/activities/emails/types/EmailDraftPrefill';
+import { type EmailThreadMessageWithSender } from '@/activities/emails/types/EmailThreadMessageWithSender';
+import { MessageParticipantRole } from 'twenty-shared/types';
+
+export const getEmailDraftPrefillFromMessage = (
+ message: EmailThreadMessageWithSender,
+): EmailDraftPrefill => {
+ const joinHandlesByRole = (role: MessageParticipantRole) =>
+ message.messageParticipants
+ .filter((participant) => participant.role === role)
+ .map((participant) => participant.handle)
+ .join(', ');
+
+ return {
+ messageId: message.id,
+ to: joinHandlesByRole(MessageParticipantRole.TO),
+ cc: joinHandlesByRole(MessageParticipantRole.CC),
+ bcc: joinHandlesByRole(MessageParticipantRole.BCC),
+ subject: message.subject,
+ body: message.text,
+ };
+};
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/email-thread/components/EmailThreadComposer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/email-thread/components/EmailThreadComposer.tsx
index 2f6b410e19..cba202fe22 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/email-thread/components/EmailThreadComposer.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/email-thread/components/EmailThreadComposer.tsx
@@ -4,11 +4,15 @@ import { useCallback, useMemo } from 'react';
import { EmailComposerFields } from '@/activities/emails/components/EmailComposerFields';
import { useEmailComposerState } from '@/activities/emails/hooks/useEmailComposerState';
import { type ReplyContextReady } from '@/activities/emails/hooks/useReplyContext';
+import { type EmailDraftPrefill } from '@/activities/emails/types/EmailDraftPrefill';
import { EmailThreadComposerFooterEffect } from '@/page-layout/widgets/email-thread/components/EmailThreadComposerFooterEffect';
import { SIDE_PANEL_FOCUS_ID } from '@/side-panel/constants/SidePanelFocusId';
+import { useOpenRecordInSidePanel } from '@/side-panel/hooks/useOpenRecordInSidePanel';
import { type SidePanelFooterCommandMenuItem } from '@/ui/layout/side-panel/types/SidePanelFooterCommandMenuItem';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { t } from '@lingui/core/macro';
+import { CoreObjectNameSingular } from 'twenty-shared/types';
+import { isDefined } from 'twenty-shared/utils';
import { IconArrowBackUp, IconSend, IconX } from 'twenty-ui/icon';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { getOsControlSymbol } from 'twenty-ui/utilities';
@@ -34,6 +38,7 @@ type EmailThreadComposerProps = {
isInSidePanel: boolean;
isComposerOpen: boolean;
setIsComposerOpen: (open: boolean) => void;
+ draftPrefill?: EmailDraftPrefill | null;
};
export const EmailThreadComposer = ({
@@ -41,13 +46,27 @@ export const EmailThreadComposer = ({
isInSidePanel,
isComposerOpen,
setIsComposerOpen,
+ draftPrefill,
}: EmailThreadComposerProps) => {
- const handleReplySent = useCallback(() => {
- setIsComposerOpen(false);
- }, [setIsComposerOpen]);
+ const { openRecordInSidePanel } = useOpenRecordInSidePanel();
+
+ const handleReplySent = useCallback(
+ (messageThreadId: string | null) => {
+ setIsComposerOpen(false);
+
+ if (isDefined(messageThreadId)) {
+ openRecordInSidePanel({
+ recordId: messageThreadId,
+ objectNameSingular: CoreObjectNameSingular.MessageThread,
+ });
+ }
+ },
+ [setIsComposerOpen, openRecordInSidePanel],
+ );
const composerState = useEmailComposerState({
connectedAccountId: replyContext.connectedAccountId,
+ draftPrefill,
defaultTo: replyContext.to,
defaultSubject: replyContext.subject,
defaultInReplyTo: replyContext.inReplyTo,
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/email-thread/components/EmailThreadIntermediaryMessages.tsx b/packages/twenty-front/src/modules/page-layout/widgets/email-thread/components/EmailThreadIntermediaryMessages.tsx
index f856c49220..02bcaa8be6 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/email-thread/components/EmailThreadIntermediaryMessages.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/email-thread/components/EmailThreadIntermediaryMessages.tsx
@@ -15,8 +15,10 @@ const StyledButtonContainer = styled.div`
export const EmailThreadIntermediaryMessages = ({
messages,
+ onDraftClick,
}: {
messages: EmailThreadMessageWithSender[];
+ onDraftClick: (message: EmailThreadMessageWithSender) => void;
}) => {
const [areMessagesOpen, setAreMessagesOpen] = useState(false);
const messagesLength = messages.length;
@@ -29,10 +31,8 @@ export const EmailThreadIntermediaryMessages = ({
messages.map((message) => (
))
) : (
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/email-thread/components/EmailThreadWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/email-thread/components/EmailThreadWidget.tsx
index 657322d9ed..fd61caa930 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/email-thread/components/EmailThreadWidget.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/email-thread/components/EmailThreadWidget.tsx
@@ -1,11 +1,14 @@
import { styled } from '@linaria/react';
-import { useState } from 'react';
+import { useCallback, useState } from 'react';
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
import { EmailLoader } from '@/activities/emails/components/EmailLoader';
import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage';
import { useEmailThread } from '@/activities/emails/hooks/useEmailThread';
import { useReplyContext } from '@/activities/emails/hooks/useReplyContext';
+import { type EmailDraftPrefill } from '@/activities/emails/types/EmailDraftPrefill';
+import { type EmailThreadMessageWithSender } from '@/activities/emails/types/EmailThreadMessageWithSender';
+import { getEmailDraftPrefillFromMessage } from '@/activities/emails/utils/getEmailDraftPrefillFromMessage';
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { EmailThreadComposer } from '@/page-layout/widgets/email-thread/components/EmailThreadComposer';
import { EmailThreadIntermediaryMessages } from '@/page-layout/widgets/email-thread/components/EmailThreadIntermediaryMessages';
@@ -43,7 +46,36 @@ export const EmailThreadWidget = ({
const replyContext = useReplyContext(targetRecord.id);
- const [isComposerOpen, setIsComposerOpen] = useState(false);
+ const [composerIntent, setComposerIntent] = useState<
+ 'opened' | 'closed' | null
+ >(null);
+ const [clickedDraftPrefill, setClickedDraftPrefill] =
+ useState(null);
+ const [previousTargetRecordId, setPreviousTargetRecordId] = useState(
+ targetRecord.id,
+ );
+
+ if (previousTargetRecordId !== targetRecord.id) {
+ setPreviousTargetRecordId(targetRecord.id);
+ setComposerIntent(null);
+ setClickedDraftPrefill(null);
+ }
+
+ const handleComposerOpenChange = useCallback((open: boolean) => {
+ setComposerIntent(open ? 'opened' : 'closed');
+
+ if (!open) {
+ setClickedDraftPrefill(null);
+ }
+ }, []);
+
+ const handleDraftClick = useCallback(
+ (message: EmailThreadMessageWithSender) => {
+ setClickedDraftPrefill(getEmailDraftPrefillFromMessage(message));
+ setComposerIntent('opened');
+ },
+ [],
+ );
const canReply = isDefined(replyContext) && !replyContext.loading;
@@ -58,6 +90,16 @@ export const EmailThreadWidget = ({
: [];
const lastMessage = messages[messagesCount - 1];
+ const trailingDraft = lastMessage?.isDraft ? lastMessage : undefined;
+ const draftPrefill =
+ clickedDraftPrefill ??
+ (isDefined(trailingDraft)
+ ? getEmailDraftPrefillFromMessage(trailingDraft)
+ : null);
+ const isComposerOpen =
+ composerIntent === 'opened' ||
+ (composerIntent === null && isDefined(trailingDraft));
+
if (threadLoading || !thread || !messages.length) {
return (
@@ -74,21 +116,20 @@ export const EmailThreadWidget = ({
{firstMessages.map((message) => (
))}
-
+
{canReply && (
)}
diff --git a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getInitialAdvancedTextEditorContent.ts b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getInitialAdvancedTextEditorContent.ts
index dda5907e48..d6b199c03c 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getInitialAdvancedTextEditorContent.ts
+++ b/packages/twenty-front/src/modules/workflow/workflow-variables/utils/getInitialAdvancedTextEditorContent.ts
@@ -1,6 +1,5 @@
import { getInitialEditorContent } from '@/workflow/workflow-variables/utils/getInitialEditorContent';
import type { JSONContent } from '@tiptap/react';
-import { logError } from '~/utils/logError';
// Previous format of the email body was plain text,
// but from now on we will save it as JSON.
@@ -33,8 +32,7 @@ export const getInitialAdvancedTextEditorContent = (
}
return json;
- } catch (error) {
- logError(error);
+ } catch {
return getInitialEditorContent(rawContent);
}
};
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-upgrade-version-command.module.ts
index e5631e6beb..515548774f 100644
--- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-upgrade-version-command.module.ts
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-upgrade-version-command.module.ts
@@ -1,16 +1,21 @@
import { Module } from '@nestjs/common';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
+import { AddMessageIsDraftFieldCommand } from 'src/database/commands/upgrade-version-command/2-18/2-18-workspace-command-1810000005000-add-message-is-draft-field.command';
import { NormalizeLegacyIndexNamesCommand } from 'src/database/commands/upgrade-version-command/2-18/2-18-workspace-command-1799200000000-normalize-legacy-index-names.command';
+import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
+import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
@Module({
imports: [
+ ApplicationModule,
WorkspaceCacheModule,
WorkspaceIteratorModule,
+ WorkspaceMigrationModule,
WorkspaceSchemaManagerModule,
],
- providers: [NormalizeLegacyIndexNamesCommand],
+ providers: [AddMessageIsDraftFieldCommand, NormalizeLegacyIndexNamesCommand],
})
export class V2_18_UpgradeVersionCommandModule {}
diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-workspace-command-1810000005000-add-message-is-draft-field.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-workspace-command-1810000005000-add-message-is-draft-field.command.ts
new file mode 100644
index 0000000000..7b80a4ba97
--- /dev/null
+++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-workspace-command-1810000005000-add-message-is-draft-field.command.ts
@@ -0,0 +1,144 @@
+import { Command } from 'nest-commander';
+import { isDefined } from 'twenty-shared/utils';
+
+import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
+import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
+import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
+import { ApplicationService } from 'src/engine/core-modules/application/application.service';
+import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
+import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
+import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
+import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
+import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
+import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
+import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
+
+const MESSAGE_OBJECT_UNIVERSAL_IDENTIFIER =
+ '20202020-3f6b-4425-80ab-e468899ab4b2';
+const MESSAGE_IS_DRAFT_FIELD_UNIVERSAL_IDENTIFIER =
+ '20202020-4d3a-4b6e-9c1f-2a5e7b9d0c34';
+
+@RegisteredWorkspaceCommand('2.18.0', 1810000005000)
+@Command({
+ name: 'upgrade:2-18:add-message-is-draft-field',
+ description:
+ 'Add the Message isDraft field metadata and column to existing workspaces',
+})
+export class AddMessageIsDraftFieldCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
+ constructor(
+ protected readonly workspaceIteratorService: WorkspaceIteratorService,
+ private readonly applicationService: ApplicationService,
+ private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
+ private readonly workspaceCacheService: WorkspaceCacheService,
+ ) {
+ super(workspaceIteratorService);
+ }
+
+ override async runOnWorkspace({
+ workspaceId,
+ options,
+ }: RunOnWorkspaceArgs): Promise {
+ const isDryRun = options.dryRun ?? false;
+
+ const { flatFieldMetadataMaps, flatObjectMetadataMaps } =
+ await this.workspaceCacheService.getOrRecompute(workspaceId, [
+ 'flatFieldMetadataMaps',
+ 'flatObjectMetadataMaps',
+ ]);
+
+ const messageObjectMetadata =
+ findFlatEntityByUniversalIdentifier({
+ flatEntityMaps: flatObjectMetadataMaps,
+ universalIdentifier: MESSAGE_OBJECT_UNIVERSAL_IDENTIFIER,
+ });
+
+ if (!isDefined(messageObjectMetadata)) {
+ this.logger.log(
+ `Message object does not exist for workspace ${workspaceId}, skipping`,
+ );
+
+ return;
+ }
+
+ const existingIsDraftFieldMetadata =
+ findFlatEntityByUniversalIdentifier({
+ flatEntityMaps: flatFieldMetadataMaps,
+ universalIdentifier: MESSAGE_IS_DRAFT_FIELD_UNIVERSAL_IDENTIFIER,
+ });
+
+ if (isDefined(existingIsDraftFieldMetadata)) {
+ this.logger.log(
+ `Message isDraft field already present for workspace ${workspaceId}`,
+ );
+
+ return;
+ }
+
+ const { twentyStandardFlatApplication } =
+ await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
+ { workspaceId },
+ );
+
+ const { allFlatEntityMaps: standardAllFlatEntityMaps } =
+ computeTwentyStandardApplicationAllFlatEntityMaps({
+ now: new Date().toISOString(),
+ workspaceId,
+ twentyStandardApplicationId: twentyStandardFlatApplication.id,
+ });
+
+ const isDraftFlatFieldMetadata =
+ findFlatEntityByUniversalIdentifier({
+ flatEntityMaps: standardAllFlatEntityMaps.flatFieldMetadataMaps,
+ universalIdentifier: MESSAGE_IS_DRAFT_FIELD_UNIVERSAL_IDENTIFIER,
+ });
+
+ if (!isDefined(isDraftFlatFieldMetadata)) {
+ throw new Error(
+ 'Standard application is missing the Message isDraft field metadata',
+ );
+ }
+
+ if (isDryRun) {
+ this.logger.log(
+ `[DRY RUN] Would create Message isDraft field for workspace ${workspaceId}`,
+ );
+
+ return;
+ }
+
+ const validateAndBuildResult =
+ await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
+ {
+ isSystemBuild: true,
+ allFlatEntityOperationByMetadataName: {
+ fieldMetadata: {
+ flatEntityToCreate: [isDraftFlatFieldMetadata],
+ flatEntityToDelete: [],
+ flatEntityToUpdate: [],
+ },
+ },
+ workspaceId,
+ applicationUniversalIdentifier:
+ twentyStandardFlatApplication.universalIdentifier,
+ },
+ );
+
+ if (validateAndBuildResult.status === 'fail') {
+ this.logger.error(
+ `Failed to create Message isDraft field:\n${JSON.stringify(
+ validateAndBuildResult,
+ null,
+ 2,
+ )}`,
+ );
+
+ throw new Error(
+ `Failed to create Message isDraft field for workspace ${workspaceId}`,
+ );
+ }
+
+ this.logger.log(
+ `Created Message isDraft field for workspace ${workspaceId}`,
+ );
+ }
+}
diff --git a/packages/twenty-server/src/engine/core-modules/messaging/dtos/timeline-thread.dto.ts b/packages/twenty-server/src/engine/core-modules/messaging/dtos/timeline-thread.dto.ts
index 04b9abbbd8..d6c4c1cdd3 100644
--- a/packages/twenty-server/src/engine/core-modules/messaging/dtos/timeline-thread.dto.ts
+++ b/packages/twenty-server/src/engine/core-modules/messaging/dtos/timeline-thread.dto.ts
@@ -35,4 +35,7 @@ export class TimelineThreadDTO {
@Field()
participantCount: number;
+
+ @Field()
+ lastMessageIsDraft: boolean;
}
diff --git a/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts b/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts
index f3535db298..ed2f578fdd 100644
--- a/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts
@@ -106,6 +106,7 @@ export class TimelineMessagingService {
lastMessageBody: lastMessage.text ?? '',
lastMessageReceivedAt: lastMessage.receivedAt ?? new Date(),
numberOfMessagesInThread: messageThread.messages.length,
+ lastMessageIsDraft: lastMessage.isDraft ?? false,
};
}),
totalNumberOfThreads,
diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap
index 31287b3588..1bd4919d98 100644
--- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap
+++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap
@@ -909,7 +909,7 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"id": "00000000-0000-0000-0000-000000000418",
},
"createdBy": {
- "id": "00000000-0000-0000-0000-000000000430",
+ "id": "00000000-0000-0000-0000-000000000431",
},
"deletedAt": {
"id": "00000000-0000-0000-0000-000000000420",
@@ -923,6 +923,9 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"id": {
"id": "00000000-0000-0000-0000-000000000417",
},
+ "isDraft": {
+ "id": "00000000-0000-0000-0000-000000000430",
+ },
"messageCampaign": {
"id": "00000000-0000-0000-0000-000000000428",
},
@@ -936,13 +939,13 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"id": "00000000-0000-0000-0000-000000000422",
},
"position": {
- "id": "00000000-0000-0000-0000-000000000432",
+ "id": "00000000-0000-0000-0000-000000000433",
},
"receivedAt": {
"id": "00000000-0000-0000-0000-000000000425",
},
"searchVector": {
- "id": "00000000-0000-0000-0000-000000000433",
+ "id": "00000000-0000-0000-0000-000000000434",
},
"subject": {
"id": "00000000-0000-0000-0000-000000000423",
@@ -954,35 +957,35 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"id": "00000000-0000-0000-0000-000000000419",
},
"updatedBy": {
- "id": "00000000-0000-0000-0000-000000000431",
+ "id": "00000000-0000-0000-0000-000000000432",
},
},
- "id": "00000000-0000-0000-0000-000000000442",
+ "id": "00000000-0000-0000-0000-000000000443",
"views": {
"allMessages": {
- "id": "00000000-0000-0000-0000-000000000441",
+ "id": "00000000-0000-0000-0000-000000000442",
"viewFieldGroups": {},
"viewFields": {
"createdAt": {
- "id": "00000000-0000-0000-0000-000000000440",
+ "id": "00000000-0000-0000-0000-000000000441",
},
"headerMessageId": {
- "id": "00000000-0000-0000-0000-000000000438",
+ "id": "00000000-0000-0000-0000-000000000439",
},
"messageParticipants": {
- "id": "00000000-0000-0000-0000-000000000436",
- },
- "messageThread": {
- "id": "00000000-0000-0000-0000-000000000435",
- },
- "receivedAt": {
"id": "00000000-0000-0000-0000-000000000437",
},
+ "messageThread": {
+ "id": "00000000-0000-0000-0000-000000000436",
+ },
+ "receivedAt": {
+ "id": "00000000-0000-0000-0000-000000000438",
+ },
"subject": {
- "id": "00000000-0000-0000-0000-000000000434",
+ "id": "00000000-0000-0000-0000-000000000435",
},
"text": {
- "id": "00000000-0000-0000-0000-000000000439",
+ "id": "00000000-0000-0000-0000-000000000440",
},
},
"viewGroups": {},
@@ -1489,104 +1492,104 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"note": {
"fields": {
"attachments": {
- "id": "00000000-0000-0000-0000-000000000453",
- },
- "bodyV2": {
- "id": "00000000-0000-0000-0000-000000000449",
- },
- "createdAt": {
- "id": "00000000-0000-0000-0000-000000000444",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000450",
- },
- "deletedAt": {
- "id": "00000000-0000-0000-0000-000000000446",
- },
- "id": {
- "id": "00000000-0000-0000-0000-000000000443",
- },
- "noteTargets": {
- "id": "00000000-0000-0000-0000-000000000452",
- },
- "position": {
- "id": "00000000-0000-0000-0000-000000000447",
- },
- "searchVector": {
- "id": "00000000-0000-0000-0000-000000000455",
- },
- "timelineActivities": {
"id": "00000000-0000-0000-0000-000000000454",
},
- "title": {
- "id": "00000000-0000-0000-0000-000000000448",
+ "bodyV2": {
+ "id": "00000000-0000-0000-0000-000000000450",
},
- "updatedAt": {
+ "createdAt": {
"id": "00000000-0000-0000-0000-000000000445",
},
- "updatedBy": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000451",
},
+ "deletedAt": {
+ "id": "00000000-0000-0000-0000-000000000447",
+ },
+ "id": {
+ "id": "00000000-0000-0000-0000-000000000444",
+ },
+ "noteTargets": {
+ "id": "00000000-0000-0000-0000-000000000453",
+ },
+ "position": {
+ "id": "00000000-0000-0000-0000-000000000448",
+ },
+ "searchVector": {
+ "id": "00000000-0000-0000-0000-000000000456",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000455",
+ },
+ "title": {
+ "id": "00000000-0000-0000-0000-000000000449",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000446",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000452",
+ },
},
- "id": "00000000-0000-0000-0000-000000000473",
+ "id": "00000000-0000-0000-0000-000000000474",
"views": {
"allNotes": {
- "id": "00000000-0000-0000-0000-000000000461",
+ "id": "00000000-0000-0000-0000-000000000462",
"viewFieldGroups": {},
"viewFields": {
"bodyV2": {
- "id": "00000000-0000-0000-0000-000000000458",
- },
- "createdAt": {
- "id": "00000000-0000-0000-0000-000000000460",
- },
- "createdBy": {
"id": "00000000-0000-0000-0000-000000000459",
},
+ "createdAt": {
+ "id": "00000000-0000-0000-0000-000000000461",
+ },
+ "createdBy": {
+ "id": "00000000-0000-0000-0000-000000000460",
+ },
"noteTargets": {
- "id": "00000000-0000-0000-0000-000000000457",
+ "id": "00000000-0000-0000-0000-000000000458",
},
"title": {
- "id": "00000000-0000-0000-0000-000000000456",
+ "id": "00000000-0000-0000-0000-000000000457",
},
},
"viewGroups": {},
},
"noteRecordPageFields": {
- "id": "00000000-0000-0000-0000-000000000472",
+ "id": "00000000-0000-0000-0000-000000000473",
"viewFieldGroups": {
"general": {
- "id": "00000000-0000-0000-0000-000000000470",
+ "id": "00000000-0000-0000-0000-000000000471",
},
"system": {
- "id": "00000000-0000-0000-0000-000000000471",
+ "id": "00000000-0000-0000-0000-000000000472",
},
},
"viewFields": {
"attachments": {
- "id": "00000000-0000-0000-0000-000000000468",
- },
- "bodyV2": {
- "id": "00000000-0000-0000-0000-000000000465",
- },
- "createdAt": {
- "id": "00000000-0000-0000-0000-000000000462",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000463",
- },
- "noteTargets": {
- "id": "00000000-0000-0000-0000-000000000464",
- },
- "timelineActivities": {
"id": "00000000-0000-0000-0000-000000000469",
},
- "updatedAt": {
+ "bodyV2": {
"id": "00000000-0000-0000-0000-000000000466",
},
- "updatedBy": {
+ "createdAt": {
+ "id": "00000000-0000-0000-0000-000000000463",
+ },
+ "createdBy": {
+ "id": "00000000-0000-0000-0000-000000000464",
+ },
+ "noteTargets": {
+ "id": "00000000-0000-0000-0000-000000000465",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000470",
+ },
+ "updatedAt": {
"id": "00000000-0000-0000-0000-000000000467",
},
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000468",
+ },
},
"viewGroups": {},
},
@@ -1595,62 +1598,62 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"noteTarget": {
"fields": {
"createdAt": {
- "id": "00000000-0000-0000-0000-000000000475",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000482",
- },
- "deletedAt": {
- "id": "00000000-0000-0000-0000-000000000477",
- },
- "id": {
- "id": "00000000-0000-0000-0000-000000000474",
- },
- "note": {
- "id": "00000000-0000-0000-0000-000000000478",
- },
- "position": {
- "id": "00000000-0000-0000-0000-000000000484",
- },
- "searchVector": {
- "id": "00000000-0000-0000-0000-000000000485",
- },
- "targetCompany": {
- "id": "00000000-0000-0000-0000-000000000480",
- },
- "targetOpportunity": {
- "id": "00000000-0000-0000-0000-000000000481",
- },
- "targetPerson": {
- "id": "00000000-0000-0000-0000-000000000479",
- },
- "updatedAt": {
"id": "00000000-0000-0000-0000-000000000476",
},
- "updatedBy": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000483",
},
+ "deletedAt": {
+ "id": "00000000-0000-0000-0000-000000000478",
+ },
+ "id": {
+ "id": "00000000-0000-0000-0000-000000000475",
+ },
+ "note": {
+ "id": "00000000-0000-0000-0000-000000000479",
+ },
+ "position": {
+ "id": "00000000-0000-0000-0000-000000000485",
+ },
+ "searchVector": {
+ "id": "00000000-0000-0000-0000-000000000486",
+ },
+ "targetCompany": {
+ "id": "00000000-0000-0000-0000-000000000481",
+ },
+ "targetOpportunity": {
+ "id": "00000000-0000-0000-0000-000000000482",
+ },
+ "targetPerson": {
+ "id": "00000000-0000-0000-0000-000000000480",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000477",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000484",
+ },
},
- "id": "00000000-0000-0000-0000-000000000492",
+ "id": "00000000-0000-0000-0000-000000000493",
"views": {
"allNoteTargets": {
- "id": "00000000-0000-0000-0000-000000000491",
+ "id": "00000000-0000-0000-0000-000000000492",
"viewFieldGroups": {},
"viewFields": {
"id": {
- "id": "00000000-0000-0000-0000-000000000486",
- },
- "note": {
"id": "00000000-0000-0000-0000-000000000487",
},
- "targetCompany": {
- "id": "00000000-0000-0000-0000-000000000489",
+ "note": {
+ "id": "00000000-0000-0000-0000-000000000488",
},
- "targetOpportunity": {
+ "targetCompany": {
"id": "00000000-0000-0000-0000-000000000490",
},
+ "targetOpportunity": {
+ "id": "00000000-0000-0000-0000-000000000491",
+ },
"targetPerson": {
- "id": "00000000-0000-0000-0000-000000000488",
+ "id": "00000000-0000-0000-0000-000000000489",
},
},
"viewGroups": {},
@@ -1660,187 +1663,187 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"opportunity": {
"fields": {
"amount": {
- "id": "00000000-0000-0000-0000-000000000498",
- },
- "attachments": {
- "id": "00000000-0000-0000-0000-000000000509",
- },
- "closeDate": {
"id": "00000000-0000-0000-0000-000000000499",
},
- "company": {
- "id": "00000000-0000-0000-0000-000000000505",
- },
- "createdAt": {
- "id": "00000000-0000-0000-0000-000000000494",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000502",
- },
- "deletedAt": {
- "id": "00000000-0000-0000-0000-000000000496",
- },
- "id": {
- "id": "00000000-0000-0000-0000-000000000493",
- },
- "name": {
- "id": "00000000-0000-0000-0000-000000000497",
- },
- "noteTargets": {
- "id": "00000000-0000-0000-0000-000000000508",
- },
- "owner": {
- "id": "00000000-0000-0000-0000-000000000506",
- },
- "pointOfContact": {
- "id": "00000000-0000-0000-0000-000000000504",
- },
- "position": {
- "id": "00000000-0000-0000-0000-000000000501",
- },
- "searchVector": {
- "id": "00000000-0000-0000-0000-000000000511",
- },
- "stage": {
- "id": "00000000-0000-0000-0000-000000000500",
- },
- "taskTargets": {
- "id": "00000000-0000-0000-0000-000000000507",
- },
- "timelineActivities": {
+ "attachments": {
"id": "00000000-0000-0000-0000-000000000510",
},
- "updatedAt": {
+ "closeDate": {
+ "id": "00000000-0000-0000-0000-000000000500",
+ },
+ "company": {
+ "id": "00000000-0000-0000-0000-000000000506",
+ },
+ "createdAt": {
"id": "00000000-0000-0000-0000-000000000495",
},
- "updatedBy": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000503",
},
+ "deletedAt": {
+ "id": "00000000-0000-0000-0000-000000000497",
+ },
+ "id": {
+ "id": "00000000-0000-0000-0000-000000000494",
+ },
+ "name": {
+ "id": "00000000-0000-0000-0000-000000000498",
+ },
+ "noteTargets": {
+ "id": "00000000-0000-0000-0000-000000000509",
+ },
+ "owner": {
+ "id": "00000000-0000-0000-0000-000000000507",
+ },
+ "pointOfContact": {
+ "id": "00000000-0000-0000-0000-000000000505",
+ },
+ "position": {
+ "id": "00000000-0000-0000-0000-000000000502",
+ },
+ "searchVector": {
+ "id": "00000000-0000-0000-0000-000000000512",
+ },
+ "stage": {
+ "id": "00000000-0000-0000-0000-000000000501",
+ },
+ "taskTargets": {
+ "id": "00000000-0000-0000-0000-000000000508",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000511",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000496",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000504",
+ },
},
- "id": "00000000-0000-0000-0000-000000000549",
+ "id": "00000000-0000-0000-0000-000000000550",
"views": {
"allOpportunities": {
- "id": "00000000-0000-0000-0000-000000000518",
+ "id": "00000000-0000-0000-0000-000000000519",
"viewFieldGroups": {},
"viewFields": {
"amount": {
- "id": "00000000-0000-0000-0000-000000000513",
- },
- "closeDate": {
- "id": "00000000-0000-0000-0000-000000000515",
- },
- "company": {
- "id": "00000000-0000-0000-0000-000000000516",
- },
- "createdBy": {
"id": "00000000-0000-0000-0000-000000000514",
},
+ "closeDate": {
+ "id": "00000000-0000-0000-0000-000000000516",
+ },
+ "company": {
+ "id": "00000000-0000-0000-0000-000000000517",
+ },
+ "createdBy": {
+ "id": "00000000-0000-0000-0000-000000000515",
+ },
"name": {
- "id": "00000000-0000-0000-0000-000000000512",
+ "id": "00000000-0000-0000-0000-000000000513",
},
"pointOfContact": {
- "id": "00000000-0000-0000-0000-000000000517",
+ "id": "00000000-0000-0000-0000-000000000518",
},
},
"viewGroups": {},
},
"byStage": {
- "id": "00000000-0000-0000-0000-000000000530",
+ "id": "00000000-0000-0000-0000-000000000531",
"viewFieldGroups": {},
"viewFields": {
"amount": {
- "id": "00000000-0000-0000-0000-000000000520",
- },
- "closeDate": {
- "id": "00000000-0000-0000-0000-000000000522",
- },
- "company": {
- "id": "00000000-0000-0000-0000-000000000523",
- },
- "createdBy": {
"id": "00000000-0000-0000-0000-000000000521",
},
+ "closeDate": {
+ "id": "00000000-0000-0000-0000-000000000523",
+ },
+ "company": {
+ "id": "00000000-0000-0000-0000-000000000524",
+ },
+ "createdBy": {
+ "id": "00000000-0000-0000-0000-000000000522",
+ },
"name": {
- "id": "00000000-0000-0000-0000-000000000519",
+ "id": "00000000-0000-0000-0000-000000000520",
},
"pointOfContact": {
- "id": "00000000-0000-0000-0000-000000000524",
+ "id": "00000000-0000-0000-0000-000000000525",
},
},
"viewGroups": {
"customer": {
- "id": "00000000-0000-0000-0000-000000000529",
+ "id": "00000000-0000-0000-0000-000000000530",
},
"meeting": {
- "id": "00000000-0000-0000-0000-000000000527",
- },
- "new": {
- "id": "00000000-0000-0000-0000-000000000525",
- },
- "proposal": {
"id": "00000000-0000-0000-0000-000000000528",
},
- "screening": {
+ "new": {
"id": "00000000-0000-0000-0000-000000000526",
},
+ "proposal": {
+ "id": "00000000-0000-0000-0000-000000000529",
+ },
+ "screening": {
+ "id": "00000000-0000-0000-0000-000000000527",
+ },
},
},
"opportunityRecordPageFields": {
- "id": "00000000-0000-0000-0000-000000000548",
+ "id": "00000000-0000-0000-0000-000000000549",
"viewFieldGroups": {
"deal": {
- "id": "00000000-0000-0000-0000-000000000545",
- },
- "relations": {
"id": "00000000-0000-0000-0000-000000000546",
},
- "system": {
+ "relations": {
"id": "00000000-0000-0000-0000-000000000547",
},
+ "system": {
+ "id": "00000000-0000-0000-0000-000000000548",
+ },
},
"viewFields": {
"amount": {
- "id": "00000000-0000-0000-0000-000000000531",
- },
- "attachments": {
- "id": "00000000-0000-0000-0000-000000000543",
- },
- "closeDate": {
"id": "00000000-0000-0000-0000-000000000532",
},
- "company": {
- "id": "00000000-0000-0000-0000-000000000534",
- },
- "createdAt": {
- "id": "00000000-0000-0000-0000-000000000537",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000538",
- },
- "noteTargets": {
- "id": "00000000-0000-0000-0000-000000000542",
- },
- "owner": {
- "id": "00000000-0000-0000-0000-000000000536",
- },
- "pointOfContact": {
- "id": "00000000-0000-0000-0000-000000000535",
- },
- "stage": {
- "id": "00000000-0000-0000-0000-000000000533",
- },
- "taskTargets": {
- "id": "00000000-0000-0000-0000-000000000541",
- },
- "timelineActivities": {
+ "attachments": {
"id": "00000000-0000-0000-0000-000000000544",
},
- "updatedAt": {
+ "closeDate": {
+ "id": "00000000-0000-0000-0000-000000000533",
+ },
+ "company": {
+ "id": "00000000-0000-0000-0000-000000000535",
+ },
+ "createdAt": {
+ "id": "00000000-0000-0000-0000-000000000538",
+ },
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000539",
},
- "updatedBy": {
+ "noteTargets": {
+ "id": "00000000-0000-0000-0000-000000000543",
+ },
+ "owner": {
+ "id": "00000000-0000-0000-0000-000000000537",
+ },
+ "pointOfContact": {
+ "id": "00000000-0000-0000-0000-000000000536",
+ },
+ "stage": {
+ "id": "00000000-0000-0000-0000-000000000534",
+ },
+ "taskTargets": {
+ "id": "00000000-0000-0000-0000-000000000542",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000545",
+ },
+ "updatedAt": {
"id": "00000000-0000-0000-0000-000000000540",
},
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000541",
+ },
},
"viewGroups": {},
},
@@ -1849,182 +1852,182 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"person": {
"fields": {
"attachments": {
- "id": "00000000-0000-0000-0000-000000000568",
- },
- "avatarFile": {
- "id": "00000000-0000-0000-0000-000000000560",
- },
- "avatarUrl": {
- "id": "00000000-0000-0000-0000-000000000559",
- },
- "calendarEventParticipants": {
- "id": "00000000-0000-0000-0000-000000000570",
- },
- "company": {
- "id": "00000000-0000-0000-0000-000000000564",
- },
- "createdAt": {
- "id": "00000000-0000-0000-0000-000000000551",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000562",
- },
- "deletedAt": {
- "id": "00000000-0000-0000-0000-000000000553",
- },
- "emails": {
- "id": "00000000-0000-0000-0000-000000000555",
- },
- "id": {
- "id": "00000000-0000-0000-0000-000000000550",
- },
- "jobTitle": {
- "id": "00000000-0000-0000-0000-000000000557",
- },
- "linkedinLink": {
- "id": "00000000-0000-0000-0000-000000000556",
- },
- "listMemberships": {
- "id": "00000000-0000-0000-0000-000000000572",
- },
- "messageParticipants": {
"id": "00000000-0000-0000-0000-000000000569",
},
- "name": {
- "id": "00000000-0000-0000-0000-000000000554",
- },
- "noteTargets": {
- "id": "00000000-0000-0000-0000-000000000567",
- },
- "phones": {
- "id": "00000000-0000-0000-0000-000000000558",
- },
- "pointOfContactForOpportunities": {
- "id": "00000000-0000-0000-0000-000000000565",
- },
- "position": {
+ "avatarFile": {
"id": "00000000-0000-0000-0000-000000000561",
},
- "searchVector": {
- "id": "00000000-0000-0000-0000-000000000573",
+ "avatarUrl": {
+ "id": "00000000-0000-0000-0000-000000000560",
},
- "taskTargets": {
- "id": "00000000-0000-0000-0000-000000000566",
- },
- "timelineActivities": {
+ "calendarEventParticipants": {
"id": "00000000-0000-0000-0000-000000000571",
},
- "updatedAt": {
+ "company": {
+ "id": "00000000-0000-0000-0000-000000000565",
+ },
+ "createdAt": {
"id": "00000000-0000-0000-0000-000000000552",
},
- "updatedBy": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000563",
},
+ "deletedAt": {
+ "id": "00000000-0000-0000-0000-000000000554",
+ },
+ "emails": {
+ "id": "00000000-0000-0000-0000-000000000556",
+ },
+ "id": {
+ "id": "00000000-0000-0000-0000-000000000551",
+ },
+ "jobTitle": {
+ "id": "00000000-0000-0000-0000-000000000558",
+ },
+ "linkedinLink": {
+ "id": "00000000-0000-0000-0000-000000000557",
+ },
+ "listMemberships": {
+ "id": "00000000-0000-0000-0000-000000000573",
+ },
+ "messageParticipants": {
+ "id": "00000000-0000-0000-0000-000000000570",
+ },
+ "name": {
+ "id": "00000000-0000-0000-0000-000000000555",
+ },
+ "noteTargets": {
+ "id": "00000000-0000-0000-0000-000000000568",
+ },
+ "phones": {
+ "id": "00000000-0000-0000-0000-000000000559",
+ },
+ "pointOfContactForOpportunities": {
+ "id": "00000000-0000-0000-0000-000000000566",
+ },
+ "position": {
+ "id": "00000000-0000-0000-0000-000000000562",
+ },
+ "searchVector": {
+ "id": "00000000-0000-0000-0000-000000000574",
+ },
+ "taskTargets": {
+ "id": "00000000-0000-0000-0000-000000000567",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000572",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000553",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000564",
+ },
},
- "id": "00000000-0000-0000-0000-000000000606",
+ "id": "00000000-0000-0000-0000-000000000607",
"views": {
"allPeople": {
- "id": "00000000-0000-0000-0000-000000000582",
+ "id": "00000000-0000-0000-0000-000000000583",
"viewFieldGroups": {},
"viewFields": {
"company": {
- "id": "00000000-0000-0000-0000-000000000577",
+ "id": "00000000-0000-0000-0000-000000000578",
},
"createdAt": {
- "id": "00000000-0000-0000-0000-000000000579",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000576",
- },
- "emails": {
- "id": "00000000-0000-0000-0000-000000000575",
- },
- "jobTitle": {
"id": "00000000-0000-0000-0000-000000000580",
},
- "linkedinLink": {
+ "createdBy": {
+ "id": "00000000-0000-0000-0000-000000000577",
+ },
+ "emails": {
+ "id": "00000000-0000-0000-0000-000000000576",
+ },
+ "jobTitle": {
"id": "00000000-0000-0000-0000-000000000581",
},
+ "linkedinLink": {
+ "id": "00000000-0000-0000-0000-000000000582",
+ },
"name": {
- "id": "00000000-0000-0000-0000-000000000574",
+ "id": "00000000-0000-0000-0000-000000000575",
},
"phones": {
- "id": "00000000-0000-0000-0000-000000000578",
+ "id": "00000000-0000-0000-0000-000000000579",
},
},
"viewGroups": {},
},
"personRecordPageFields": {
- "id": "00000000-0000-0000-0000-000000000605",
+ "id": "00000000-0000-0000-0000-000000000606",
"viewFieldGroups": {
"general": {
- "id": "00000000-0000-0000-0000-000000000601",
+ "id": "00000000-0000-0000-0000-000000000602",
},
"social": {
- "id": "00000000-0000-0000-0000-000000000603",
- },
- "system": {
"id": "00000000-0000-0000-0000-000000000604",
},
+ "system": {
+ "id": "00000000-0000-0000-0000-000000000605",
+ },
"work": {
- "id": "00000000-0000-0000-0000-000000000602",
+ "id": "00000000-0000-0000-0000-000000000603",
},
},
"viewFields": {
"attachments": {
- "id": "00000000-0000-0000-0000-000000000597",
- },
- "avatarFile": {
- "id": "00000000-0000-0000-0000-000000000593",
- },
- "avatarUrl": {
- "id": "00000000-0000-0000-0000-000000000588",
- },
- "calendarEventParticipants": {
- "id": "00000000-0000-0000-0000-000000000599",
- },
- "company": {
- "id": "00000000-0000-0000-0000-000000000585",
- },
- "createdAt": {
- "id": "00000000-0000-0000-0000-000000000589",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000590",
- },
- "emails": {
- "id": "00000000-0000-0000-0000-000000000583",
- },
- "jobTitle": {
- "id": "00000000-0000-0000-0000-000000000586",
- },
- "linkedinLink": {
- "id": "00000000-0000-0000-0000-000000000587",
- },
- "messageParticipants": {
"id": "00000000-0000-0000-0000-000000000598",
},
- "noteTargets": {
- "id": "00000000-0000-0000-0000-000000000596",
- },
- "phones": {
- "id": "00000000-0000-0000-0000-000000000584",
- },
- "pointOfContactForOpportunities": {
+ "avatarFile": {
"id": "00000000-0000-0000-0000-000000000594",
},
- "taskTargets": {
- "id": "00000000-0000-0000-0000-000000000595",
+ "avatarUrl": {
+ "id": "00000000-0000-0000-0000-000000000589",
},
- "timelineActivities": {
+ "calendarEventParticipants": {
"id": "00000000-0000-0000-0000-000000000600",
},
- "updatedAt": {
+ "company": {
+ "id": "00000000-0000-0000-0000-000000000586",
+ },
+ "createdAt": {
+ "id": "00000000-0000-0000-0000-000000000590",
+ },
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000591",
},
- "updatedBy": {
+ "emails": {
+ "id": "00000000-0000-0000-0000-000000000584",
+ },
+ "jobTitle": {
+ "id": "00000000-0000-0000-0000-000000000587",
+ },
+ "linkedinLink": {
+ "id": "00000000-0000-0000-0000-000000000588",
+ },
+ "messageParticipants": {
+ "id": "00000000-0000-0000-0000-000000000599",
+ },
+ "noteTargets": {
+ "id": "00000000-0000-0000-0000-000000000597",
+ },
+ "phones": {
+ "id": "00000000-0000-0000-0000-000000000585",
+ },
+ "pointOfContactForOpportunities": {
+ "id": "00000000-0000-0000-0000-000000000595",
+ },
+ "taskTargets": {
+ "id": "00000000-0000-0000-0000-000000000596",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000601",
+ },
+ "updatedAt": {
"id": "00000000-0000-0000-0000-000000000592",
},
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000593",
+ },
},
"viewGroups": {},
},
@@ -2033,204 +2036,204 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"task": {
"fields": {
"assignee": {
- "id": "00000000-0000-0000-0000-000000000620",
- },
- "attachments": {
- "id": "00000000-0000-0000-0000-000000000619",
- },
- "bodyV2": {
- "id": "00000000-0000-0000-0000-000000000613",
- },
- "createdAt": {
- "id": "00000000-0000-0000-0000-000000000608",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000616",
- },
- "deletedAt": {
- "id": "00000000-0000-0000-0000-000000000610",
- },
- "dueAt": {
- "id": "00000000-0000-0000-0000-000000000614",
- },
- "id": {
- "id": "00000000-0000-0000-0000-000000000607",
- },
- "position": {
- "id": "00000000-0000-0000-0000-000000000611",
- },
- "searchVector": {
- "id": "00000000-0000-0000-0000-000000000622",
- },
- "status": {
- "id": "00000000-0000-0000-0000-000000000615",
- },
- "taskTargets": {
- "id": "00000000-0000-0000-0000-000000000618",
- },
- "timelineActivities": {
"id": "00000000-0000-0000-0000-000000000621",
},
- "title": {
- "id": "00000000-0000-0000-0000-000000000612",
+ "attachments": {
+ "id": "00000000-0000-0000-0000-000000000620",
},
- "updatedAt": {
+ "bodyV2": {
+ "id": "00000000-0000-0000-0000-000000000614",
+ },
+ "createdAt": {
"id": "00000000-0000-0000-0000-000000000609",
},
- "updatedBy": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000617",
},
+ "deletedAt": {
+ "id": "00000000-0000-0000-0000-000000000611",
+ },
+ "dueAt": {
+ "id": "00000000-0000-0000-0000-000000000615",
+ },
+ "id": {
+ "id": "00000000-0000-0000-0000-000000000608",
+ },
+ "position": {
+ "id": "00000000-0000-0000-0000-000000000612",
+ },
+ "searchVector": {
+ "id": "00000000-0000-0000-0000-000000000623",
+ },
+ "status": {
+ "id": "00000000-0000-0000-0000-000000000616",
+ },
+ "taskTargets": {
+ "id": "00000000-0000-0000-0000-000000000619",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000622",
+ },
+ "title": {
+ "id": "00000000-0000-0000-0000-000000000613",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000610",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000618",
+ },
},
- "id": "00000000-0000-0000-0000-000000000667",
+ "id": "00000000-0000-0000-0000-000000000668",
"views": {
"allTasks": {
- "id": "00000000-0000-0000-0000-000000000631",
+ "id": "00000000-0000-0000-0000-000000000632",
"viewFieldGroups": {},
"viewFields": {
"assignee": {
- "id": "00000000-0000-0000-0000-000000000628",
- },
- "bodyV2": {
"id": "00000000-0000-0000-0000-000000000629",
},
- "createdAt": {
+ "bodyV2": {
"id": "00000000-0000-0000-0000-000000000630",
},
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000626",
+ "createdAt": {
+ "id": "00000000-0000-0000-0000-000000000631",
},
- "dueAt": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000627",
},
- "status": {
- "id": "00000000-0000-0000-0000-000000000624",
+ "dueAt": {
+ "id": "00000000-0000-0000-0000-000000000628",
},
- "taskTargets": {
+ "status": {
"id": "00000000-0000-0000-0000-000000000625",
},
+ "taskTargets": {
+ "id": "00000000-0000-0000-0000-000000000626",
+ },
"title": {
- "id": "00000000-0000-0000-0000-000000000623",
+ "id": "00000000-0000-0000-0000-000000000624",
},
},
"viewGroups": {},
},
"assignedToMe": {
- "id": "00000000-0000-0000-0000-000000000643",
+ "id": "00000000-0000-0000-0000-000000000644",
"viewFieldGroups": {},
"viewFields": {
"assignee": {
- "id": "00000000-0000-0000-0000-000000000636",
- },
- "bodyV2": {
"id": "00000000-0000-0000-0000-000000000637",
},
- "createdAt": {
+ "bodyV2": {
"id": "00000000-0000-0000-0000-000000000638",
},
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000634",
+ "createdAt": {
+ "id": "00000000-0000-0000-0000-000000000639",
},
- "dueAt": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000635",
},
+ "dueAt": {
+ "id": "00000000-0000-0000-0000-000000000636",
+ },
"taskTargets": {
- "id": "00000000-0000-0000-0000-000000000633",
+ "id": "00000000-0000-0000-0000-000000000634",
},
"title": {
- "id": "00000000-0000-0000-0000-000000000632",
+ "id": "00000000-0000-0000-0000-000000000633",
},
},
"viewGroups": {
"done": {
- "id": "00000000-0000-0000-0000-000000000641",
- },
- "empty": {
"id": "00000000-0000-0000-0000-000000000642",
},
+ "empty": {
+ "id": "00000000-0000-0000-0000-000000000643",
+ },
"inProgress": {
- "id": "00000000-0000-0000-0000-000000000640",
+ "id": "00000000-0000-0000-0000-000000000641",
},
"todo": {
- "id": "00000000-0000-0000-0000-000000000639",
+ "id": "00000000-0000-0000-0000-000000000640",
},
},
},
"byStatus": {
- "id": "00000000-0000-0000-0000-000000000652",
+ "id": "00000000-0000-0000-0000-000000000653",
"viewFieldGroups": {},
"viewFields": {
"assignee": {
- "id": "00000000-0000-0000-0000-000000000647",
- },
- "createdAt": {
"id": "00000000-0000-0000-0000-000000000648",
},
+ "createdAt": {
+ "id": "00000000-0000-0000-0000-000000000649",
+ },
"dueAt": {
- "id": "00000000-0000-0000-0000-000000000646",
+ "id": "00000000-0000-0000-0000-000000000647",
},
"status": {
- "id": "00000000-0000-0000-0000-000000000645",
+ "id": "00000000-0000-0000-0000-000000000646",
},
"title": {
- "id": "00000000-0000-0000-0000-000000000644",
+ "id": "00000000-0000-0000-0000-000000000645",
},
},
"viewGroups": {
"done": {
- "id": "00000000-0000-0000-0000-000000000651",
+ "id": "00000000-0000-0000-0000-000000000652",
},
"inProgress": {
- "id": "00000000-0000-0000-0000-000000000650",
+ "id": "00000000-0000-0000-0000-000000000651",
},
"todo": {
- "id": "00000000-0000-0000-0000-000000000649",
+ "id": "00000000-0000-0000-0000-000000000650",
},
},
},
"taskRecordPageFields": {
- "id": "00000000-0000-0000-0000-000000000666",
+ "id": "00000000-0000-0000-0000-000000000667",
"viewFieldGroups": {
"general": {
- "id": "00000000-0000-0000-0000-000000000664",
+ "id": "00000000-0000-0000-0000-000000000665",
},
"system": {
- "id": "00000000-0000-0000-0000-000000000665",
+ "id": "00000000-0000-0000-0000-000000000666",
},
},
"viewFields": {
"assignee": {
- "id": "00000000-0000-0000-0000-000000000655",
- },
- "attachments": {
- "id": "00000000-0000-0000-0000-000000000662",
- },
- "bodyV2": {
- "id": "00000000-0000-0000-0000-000000000659",
- },
- "createdAt": {
"id": "00000000-0000-0000-0000-000000000656",
},
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000657",
- },
- "dueAt": {
- "id": "00000000-0000-0000-0000-000000000653",
- },
- "status": {
- "id": "00000000-0000-0000-0000-000000000654",
- },
- "taskTargets": {
- "id": "00000000-0000-0000-0000-000000000658",
- },
- "timelineActivities": {
+ "attachments": {
"id": "00000000-0000-0000-0000-000000000663",
},
- "updatedAt": {
+ "bodyV2": {
"id": "00000000-0000-0000-0000-000000000660",
},
- "updatedBy": {
+ "createdAt": {
+ "id": "00000000-0000-0000-0000-000000000657",
+ },
+ "createdBy": {
+ "id": "00000000-0000-0000-0000-000000000658",
+ },
+ "dueAt": {
+ "id": "00000000-0000-0000-0000-000000000654",
+ },
+ "status": {
+ "id": "00000000-0000-0000-0000-000000000655",
+ },
+ "taskTargets": {
+ "id": "00000000-0000-0000-0000-000000000659",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000664",
+ },
+ "updatedAt": {
"id": "00000000-0000-0000-0000-000000000661",
},
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000662",
+ },
},
"viewGroups": {},
},
@@ -2239,62 +2242,62 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"taskTarget": {
"fields": {
"createdAt": {
- "id": "00000000-0000-0000-0000-000000000669",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000676",
- },
- "deletedAt": {
- "id": "00000000-0000-0000-0000-000000000671",
- },
- "id": {
- "id": "00000000-0000-0000-0000-000000000668",
- },
- "position": {
- "id": "00000000-0000-0000-0000-000000000678",
- },
- "searchVector": {
- "id": "00000000-0000-0000-0000-000000000679",
- },
- "targetCompany": {
- "id": "00000000-0000-0000-0000-000000000674",
- },
- "targetOpportunity": {
- "id": "00000000-0000-0000-0000-000000000675",
- },
- "targetPerson": {
- "id": "00000000-0000-0000-0000-000000000673",
- },
- "task": {
- "id": "00000000-0000-0000-0000-000000000672",
- },
- "updatedAt": {
"id": "00000000-0000-0000-0000-000000000670",
},
- "updatedBy": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000677",
},
+ "deletedAt": {
+ "id": "00000000-0000-0000-0000-000000000672",
+ },
+ "id": {
+ "id": "00000000-0000-0000-0000-000000000669",
+ },
+ "position": {
+ "id": "00000000-0000-0000-0000-000000000679",
+ },
+ "searchVector": {
+ "id": "00000000-0000-0000-0000-000000000680",
+ },
+ "targetCompany": {
+ "id": "00000000-0000-0000-0000-000000000675",
+ },
+ "targetOpportunity": {
+ "id": "00000000-0000-0000-0000-000000000676",
+ },
+ "targetPerson": {
+ "id": "00000000-0000-0000-0000-000000000674",
+ },
+ "task": {
+ "id": "00000000-0000-0000-0000-000000000673",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000671",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000678",
+ },
},
- "id": "00000000-0000-0000-0000-000000000686",
+ "id": "00000000-0000-0000-0000-000000000687",
"views": {
"allTaskTargets": {
- "id": "00000000-0000-0000-0000-000000000685",
+ "id": "00000000-0000-0000-0000-000000000686",
"viewFieldGroups": {},
"viewFields": {
"id": {
- "id": "00000000-0000-0000-0000-000000000680",
+ "id": "00000000-0000-0000-0000-000000000681",
},
"targetCompany": {
- "id": "00000000-0000-0000-0000-000000000683",
- },
- "targetOpportunity": {
"id": "00000000-0000-0000-0000-000000000684",
},
+ "targetOpportunity": {
+ "id": "00000000-0000-0000-0000-000000000685",
+ },
"targetPerson": {
- "id": "00000000-0000-0000-0000-000000000682",
+ "id": "00000000-0000-0000-0000-000000000683",
},
"task": {
- "id": "00000000-0000-0000-0000-000000000681",
+ "id": "00000000-0000-0000-0000-000000000682",
},
},
"viewGroups": {},
@@ -2304,131 +2307,131 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"timelineActivity": {
"fields": {
"createdAt": {
- "id": "00000000-0000-0000-0000-000000000688",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000709",
- },
- "deletedAt": {
- "id": "00000000-0000-0000-0000-000000000690",
- },
- "happensAt": {
- "id": "00000000-0000-0000-0000-000000000691",
- },
- "id": {
- "id": "00000000-0000-0000-0000-000000000687",
- },
- "linkedObjectMetadataId": {
- "id": "00000000-0000-0000-0000-000000000708",
- },
- "linkedRecordCachedName": {
- "id": "00000000-0000-0000-0000-000000000706",
- },
- "linkedRecordId": {
- "id": "00000000-0000-0000-0000-000000000707",
- },
- "name": {
- "id": "00000000-0000-0000-0000-000000000692",
- },
- "position": {
- "id": "00000000-0000-0000-0000-000000000711",
- },
- "properties": {
- "id": "00000000-0000-0000-0000-000000000693",
- },
- "searchVector": {
- "id": "00000000-0000-0000-0000-000000000712",
- },
- "targetCompany": {
- "id": "00000000-0000-0000-0000-000000000696",
- },
- "targetDashboard": {
- "id": "00000000-0000-0000-0000-000000000703",
- },
- "targetMessageCampaign": {
- "id": "00000000-0000-0000-0000-000000000705",
- },
- "targetMessageList": {
- "id": "00000000-0000-0000-0000-000000000704",
- },
- "targetNote": {
- "id": "00000000-0000-0000-0000-000000000699",
- },
- "targetOpportunity": {
- "id": "00000000-0000-0000-0000-000000000697",
- },
- "targetPerson": {
- "id": "00000000-0000-0000-0000-000000000695",
- },
- "targetTask": {
- "id": "00000000-0000-0000-0000-000000000698",
- },
- "targetWorkflow": {
- "id": "00000000-0000-0000-0000-000000000700",
- },
- "targetWorkflowRun": {
- "id": "00000000-0000-0000-0000-000000000702",
- },
- "targetWorkflowVersion": {
- "id": "00000000-0000-0000-0000-000000000701",
- },
- "updatedAt": {
"id": "00000000-0000-0000-0000-000000000689",
},
- "updatedBy": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000710",
},
- "workspaceMember": {
+ "deletedAt": {
+ "id": "00000000-0000-0000-0000-000000000691",
+ },
+ "happensAt": {
+ "id": "00000000-0000-0000-0000-000000000692",
+ },
+ "id": {
+ "id": "00000000-0000-0000-0000-000000000688",
+ },
+ "linkedObjectMetadataId": {
+ "id": "00000000-0000-0000-0000-000000000709",
+ },
+ "linkedRecordCachedName": {
+ "id": "00000000-0000-0000-0000-000000000707",
+ },
+ "linkedRecordId": {
+ "id": "00000000-0000-0000-0000-000000000708",
+ },
+ "name": {
+ "id": "00000000-0000-0000-0000-000000000693",
+ },
+ "position": {
+ "id": "00000000-0000-0000-0000-000000000712",
+ },
+ "properties": {
"id": "00000000-0000-0000-0000-000000000694",
},
+ "searchVector": {
+ "id": "00000000-0000-0000-0000-000000000713",
+ },
+ "targetCompany": {
+ "id": "00000000-0000-0000-0000-000000000697",
+ },
+ "targetDashboard": {
+ "id": "00000000-0000-0000-0000-000000000704",
+ },
+ "targetMessageCampaign": {
+ "id": "00000000-0000-0000-0000-000000000706",
+ },
+ "targetMessageList": {
+ "id": "00000000-0000-0000-0000-000000000705",
+ },
+ "targetNote": {
+ "id": "00000000-0000-0000-0000-000000000700",
+ },
+ "targetOpportunity": {
+ "id": "00000000-0000-0000-0000-000000000698",
+ },
+ "targetPerson": {
+ "id": "00000000-0000-0000-0000-000000000696",
+ },
+ "targetTask": {
+ "id": "00000000-0000-0000-0000-000000000699",
+ },
+ "targetWorkflow": {
+ "id": "00000000-0000-0000-0000-000000000701",
+ },
+ "targetWorkflowRun": {
+ "id": "00000000-0000-0000-0000-000000000703",
+ },
+ "targetWorkflowVersion": {
+ "id": "00000000-0000-0000-0000-000000000702",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000690",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000711",
+ },
+ "workspaceMember": {
+ "id": "00000000-0000-0000-0000-000000000695",
+ },
},
- "id": "00000000-0000-0000-0000-000000000728",
+ "id": "00000000-0000-0000-0000-000000000729",
"views": {
"allTimelineActivities": {
- "id": "00000000-0000-0000-0000-000000000727",
+ "id": "00000000-0000-0000-0000-000000000728",
"viewFieldGroups": {},
"viewFields": {
"happensAt": {
- "id": "00000000-0000-0000-0000-000000000714",
- },
- "linkedRecordCachedName": {
- "id": "00000000-0000-0000-0000-000000000717",
- },
- "name": {
- "id": "00000000-0000-0000-0000-000000000713",
- },
- "properties": {
"id": "00000000-0000-0000-0000-000000000715",
},
- "targetCompany": {
- "id": "00000000-0000-0000-0000-000000000719",
- },
- "targetDashboard": {
- "id": "00000000-0000-0000-0000-000000000726",
- },
- "targetNote": {
- "id": "00000000-0000-0000-0000-000000000722",
- },
- "targetOpportunity": {
- "id": "00000000-0000-0000-0000-000000000720",
- },
- "targetPerson": {
+ "linkedRecordCachedName": {
"id": "00000000-0000-0000-0000-000000000718",
},
- "targetTask": {
- "id": "00000000-0000-0000-0000-000000000721",
+ "name": {
+ "id": "00000000-0000-0000-0000-000000000714",
},
- "targetWorkflow": {
+ "properties": {
+ "id": "00000000-0000-0000-0000-000000000716",
+ },
+ "targetCompany": {
+ "id": "00000000-0000-0000-0000-000000000720",
+ },
+ "targetDashboard": {
+ "id": "00000000-0000-0000-0000-000000000727",
+ },
+ "targetNote": {
"id": "00000000-0000-0000-0000-000000000723",
},
- "targetWorkflowRun": {
- "id": "00000000-0000-0000-0000-000000000725",
+ "targetOpportunity": {
+ "id": "00000000-0000-0000-0000-000000000721",
},
- "targetWorkflowVersion": {
+ "targetPerson": {
+ "id": "00000000-0000-0000-0000-000000000719",
+ },
+ "targetTask": {
+ "id": "00000000-0000-0000-0000-000000000722",
+ },
+ "targetWorkflow": {
"id": "00000000-0000-0000-0000-000000000724",
},
+ "targetWorkflowRun": {
+ "id": "00000000-0000-0000-0000-000000000726",
+ },
+ "targetWorkflowVersion": {
+ "id": "00000000-0000-0000-0000-000000000725",
+ },
"workspaceMember": {
- "id": "00000000-0000-0000-0000-000000000716",
+ "id": "00000000-0000-0000-0000-000000000717",
},
},
"viewGroups": {},
@@ -2438,77 +2441,77 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"workflow": {
"fields": {
"attachments": {
- "id": "00000000-0000-0000-0000-000000000741",
- },
- "automatedTriggers": {
- "id": "00000000-0000-0000-0000-000000000739",
- },
- "createdAt": {
- "id": "00000000-0000-0000-0000-000000000730",
- },
- "createdBy": {
"id": "00000000-0000-0000-0000-000000000742",
},
- "deletedAt": {
- "id": "00000000-0000-0000-0000-000000000732",
- },
- "id": {
- "id": "00000000-0000-0000-0000-000000000729",
- },
- "lastPublishedVersionId": {
- "id": "00000000-0000-0000-0000-000000000734",
- },
- "name": {
- "id": "00000000-0000-0000-0000-000000000733",
- },
- "position": {
- "id": "00000000-0000-0000-0000-000000000736",
- },
- "runs": {
- "id": "00000000-0000-0000-0000-000000000738",
- },
- "searchVector": {
- "id": "00000000-0000-0000-0000-000000000744",
- },
- "statuses": {
- "id": "00000000-0000-0000-0000-000000000735",
- },
- "timelineActivities": {
+ "automatedTriggers": {
"id": "00000000-0000-0000-0000-000000000740",
},
- "updatedAt": {
+ "createdAt": {
"id": "00000000-0000-0000-0000-000000000731",
},
- "updatedBy": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000743",
},
- "versions": {
+ "deletedAt": {
+ "id": "00000000-0000-0000-0000-000000000733",
+ },
+ "id": {
+ "id": "00000000-0000-0000-0000-000000000730",
+ },
+ "lastPublishedVersionId": {
+ "id": "00000000-0000-0000-0000-000000000735",
+ },
+ "name": {
+ "id": "00000000-0000-0000-0000-000000000734",
+ },
+ "position": {
"id": "00000000-0000-0000-0000-000000000737",
},
+ "runs": {
+ "id": "00000000-0000-0000-0000-000000000739",
+ },
+ "searchVector": {
+ "id": "00000000-0000-0000-0000-000000000745",
+ },
+ "statuses": {
+ "id": "00000000-0000-0000-0000-000000000736",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000741",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000732",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000744",
+ },
+ "versions": {
+ "id": "00000000-0000-0000-0000-000000000738",
+ },
},
- "id": "00000000-0000-0000-0000-000000000752",
+ "id": "00000000-0000-0000-0000-000000000753",
"views": {
"allWorkflows": {
- "id": "00000000-0000-0000-0000-000000000751",
+ "id": "00000000-0000-0000-0000-000000000752",
"viewFieldGroups": {},
"viewFields": {
"createdBy": {
- "id": "00000000-0000-0000-0000-000000000748",
+ "id": "00000000-0000-0000-0000-000000000749",
},
"name": {
- "id": "00000000-0000-0000-0000-000000000745",
- },
- "runs": {
- "id": "00000000-0000-0000-0000-000000000750",
- },
- "statuses": {
"id": "00000000-0000-0000-0000-000000000746",
},
- "updatedAt": {
+ "runs": {
+ "id": "00000000-0000-0000-0000-000000000751",
+ },
+ "statuses": {
"id": "00000000-0000-0000-0000-000000000747",
},
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000748",
+ },
"versions": {
- "id": "00000000-0000-0000-0000-000000000749",
+ "id": "00000000-0000-0000-0000-000000000750",
},
},
"viewGroups": {},
@@ -2518,79 +2521,79 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"workflowAutomatedTrigger": {
"fields": {
"createdAt": {
- "id": "00000000-0000-0000-0000-000000000754",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000760",
- },
- "deletedAt": {
- "id": "00000000-0000-0000-0000-000000000756",
- },
- "id": {
- "id": "00000000-0000-0000-0000-000000000753",
- },
- "position": {
- "id": "00000000-0000-0000-0000-000000000762",
- },
- "searchVector": {
- "id": "00000000-0000-0000-0000-000000000763",
- },
- "settings": {
- "id": "00000000-0000-0000-0000-000000000758",
- },
- "type": {
- "id": "00000000-0000-0000-0000-000000000757",
- },
- "updatedAt": {
"id": "00000000-0000-0000-0000-000000000755",
},
- "updatedBy": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000761",
},
- "workflow": {
+ "deletedAt": {
+ "id": "00000000-0000-0000-0000-000000000757",
+ },
+ "id": {
+ "id": "00000000-0000-0000-0000-000000000754",
+ },
+ "position": {
+ "id": "00000000-0000-0000-0000-000000000763",
+ },
+ "searchVector": {
+ "id": "00000000-0000-0000-0000-000000000764",
+ },
+ "settings": {
"id": "00000000-0000-0000-0000-000000000759",
},
+ "type": {
+ "id": "00000000-0000-0000-0000-000000000758",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000756",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000762",
+ },
+ "workflow": {
+ "id": "00000000-0000-0000-0000-000000000760",
+ },
},
- "id": "00000000-0000-0000-0000-000000000775",
+ "id": "00000000-0000-0000-0000-000000000776",
"views": {
"allWorkflowAutomatedTriggers": {
- "id": "00000000-0000-0000-0000-000000000767",
+ "id": "00000000-0000-0000-0000-000000000768",
"viewFieldGroups": {},
"viewFields": {
"createdAt": {
- "id": "00000000-0000-0000-0000-000000000766",
+ "id": "00000000-0000-0000-0000-000000000767",
},
"type": {
- "id": "00000000-0000-0000-0000-000000000764",
+ "id": "00000000-0000-0000-0000-000000000765",
},
"workflow": {
- "id": "00000000-0000-0000-0000-000000000765",
+ "id": "00000000-0000-0000-0000-000000000766",
},
},
"viewGroups": {},
},
"workflowAutomatedTriggerRecordPageFields": {
- "id": "00000000-0000-0000-0000-000000000774",
+ "id": "00000000-0000-0000-0000-000000000775",
"viewFieldGroups": {
"general": {
- "id": "00000000-0000-0000-0000-000000000772",
+ "id": "00000000-0000-0000-0000-000000000773",
},
"system": {
- "id": "00000000-0000-0000-0000-000000000773",
+ "id": "00000000-0000-0000-0000-000000000774",
},
},
"viewFields": {
"createdAt": {
- "id": "00000000-0000-0000-0000-000000000770",
- },
- "createdBy": {
"id": "00000000-0000-0000-0000-000000000771",
},
+ "createdBy": {
+ "id": "00000000-0000-0000-0000-000000000772",
+ },
"type": {
- "id": "00000000-0000-0000-0000-000000000768",
+ "id": "00000000-0000-0000-0000-000000000769",
},
"workflow": {
- "id": "00000000-0000-0000-0000-000000000769",
+ "id": "00000000-0000-0000-0000-000000000770",
},
},
"viewGroups": {},
@@ -2600,134 +2603,134 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"workflowRun": {
"fields": {
"createdAt": {
- "id": "00000000-0000-0000-0000-000000000777",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000788",
- },
- "deletedAt": {
- "id": "00000000-0000-0000-0000-000000000779",
- },
- "endedAt": {
- "id": "00000000-0000-0000-0000-000000000785",
- },
- "enqueuedAt": {
- "id": "00000000-0000-0000-0000-000000000783",
- },
- "id": {
- "id": "00000000-0000-0000-0000-000000000776",
- },
- "name": {
- "id": "00000000-0000-0000-0000-000000000780",
- },
- "position": {
- "id": "00000000-0000-0000-0000-000000000787",
- },
- "searchVector": {
- "id": "00000000-0000-0000-0000-000000000793",
- },
- "startedAt": {
- "id": "00000000-0000-0000-0000-000000000784",
- },
- "state": {
- "id": "00000000-0000-0000-0000-000000000790",
- },
- "status": {
- "id": "00000000-0000-0000-0000-000000000786",
- },
- "stepLogs": {
- "id": "00000000-0000-0000-0000-000000000791",
- },
- "timelineActivities": {
- "id": "00000000-0000-0000-0000-000000000792",
- },
- "updatedAt": {
"id": "00000000-0000-0000-0000-000000000778",
},
- "updatedBy": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000789",
},
- "workflow": {
- "id": "00000000-0000-0000-0000-000000000782",
+ "deletedAt": {
+ "id": "00000000-0000-0000-0000-000000000780",
},
- "workflowVersion": {
+ "endedAt": {
+ "id": "00000000-0000-0000-0000-000000000786",
+ },
+ "enqueuedAt": {
+ "id": "00000000-0000-0000-0000-000000000784",
+ },
+ "id": {
+ "id": "00000000-0000-0000-0000-000000000777",
+ },
+ "name": {
"id": "00000000-0000-0000-0000-000000000781",
},
+ "position": {
+ "id": "00000000-0000-0000-0000-000000000788",
+ },
+ "searchVector": {
+ "id": "00000000-0000-0000-0000-000000000794",
+ },
+ "startedAt": {
+ "id": "00000000-0000-0000-0000-000000000785",
+ },
+ "state": {
+ "id": "00000000-0000-0000-0000-000000000791",
+ },
+ "status": {
+ "id": "00000000-0000-0000-0000-000000000787",
+ },
+ "stepLogs": {
+ "id": "00000000-0000-0000-0000-000000000792",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000793",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000779",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000790",
+ },
+ "workflow": {
+ "id": "00000000-0000-0000-0000-000000000783",
+ },
+ "workflowVersion": {
+ "id": "00000000-0000-0000-0000-000000000782",
+ },
},
- "id": "00000000-0000-0000-0000-000000000816",
+ "id": "00000000-0000-0000-0000-000000000817",
"views": {
"allWorkflowRuns": {
- "id": "00000000-0000-0000-0000-000000000800",
+ "id": "00000000-0000-0000-0000-000000000801",
"viewFieldGroups": {},
"viewFields": {
"createdBy": {
- "id": "00000000-0000-0000-0000-000000000798",
+ "id": "00000000-0000-0000-0000-000000000799",
},
"name": {
- "id": "00000000-0000-0000-0000-000000000794",
- },
- "startedAt": {
- "id": "00000000-0000-0000-0000-000000000797",
- },
- "status": {
- "id": "00000000-0000-0000-0000-000000000796",
- },
- "workflow": {
"id": "00000000-0000-0000-0000-000000000795",
},
+ "startedAt": {
+ "id": "00000000-0000-0000-0000-000000000798",
+ },
+ "status": {
+ "id": "00000000-0000-0000-0000-000000000797",
+ },
+ "workflow": {
+ "id": "00000000-0000-0000-0000-000000000796",
+ },
"workflowVersion": {
- "id": "00000000-0000-0000-0000-000000000799",
+ "id": "00000000-0000-0000-0000-000000000800",
},
},
"viewGroups": {},
},
"workflowRunRecordPageFields": {
- "id": "00000000-0000-0000-0000-000000000815",
+ "id": "00000000-0000-0000-0000-000000000816",
"viewFieldGroups": {
"general": {
- "id": "00000000-0000-0000-0000-000000000813",
+ "id": "00000000-0000-0000-0000-000000000814",
},
"system": {
- "id": "00000000-0000-0000-0000-000000000814",
+ "id": "00000000-0000-0000-0000-000000000815",
},
},
"viewFields": {
"createdAt": {
- "id": "00000000-0000-0000-0000-000000000806",
- },
- "createdBy": {
"id": "00000000-0000-0000-0000-000000000807",
},
- "endedAt": {
- "id": "00000000-0000-0000-0000-000000000805",
- },
- "enqueuedAt": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000808",
},
- "startedAt": {
- "id": "00000000-0000-0000-0000-000000000804",
+ "endedAt": {
+ "id": "00000000-0000-0000-0000-000000000806",
},
- "state": {
+ "enqueuedAt": {
"id": "00000000-0000-0000-0000-000000000809",
},
- "status": {
- "id": "00000000-0000-0000-0000-000000000801",
+ "startedAt": {
+ "id": "00000000-0000-0000-0000-000000000805",
},
- "timelineActivities": {
- "id": "00000000-0000-0000-0000-000000000812",
- },
- "updatedAt": {
+ "state": {
"id": "00000000-0000-0000-0000-000000000810",
},
- "updatedBy": {
- "id": "00000000-0000-0000-0000-000000000811",
- },
- "workflow": {
+ "status": {
"id": "00000000-0000-0000-0000-000000000802",
},
- "workflowVersion": {
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000813",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000811",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000812",
+ },
+ "workflow": {
"id": "00000000-0000-0000-0000-000000000803",
},
+ "workflowVersion": {
+ "id": "00000000-0000-0000-0000-000000000804",
+ },
},
"viewGroups": {},
},
@@ -2736,115 +2739,115 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"workflowVersion": {
"fields": {
"createdAt": {
- "id": "00000000-0000-0000-0000-000000000818",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000830",
- },
- "deletedAt": {
- "id": "00000000-0000-0000-0000-000000000820",
- },
- "id": {
- "id": "00000000-0000-0000-0000-000000000817",
- },
- "name": {
- "id": "00000000-0000-0000-0000-000000000821",
- },
- "position": {
- "id": "00000000-0000-0000-0000-000000000825",
- },
- "runs": {
- "id": "00000000-0000-0000-0000-000000000826",
- },
- "searchVector": {
- "id": "00000000-0000-0000-0000-000000000829",
- },
- "status": {
- "id": "00000000-0000-0000-0000-000000000824",
- },
- "steps": {
- "id": "00000000-0000-0000-0000-000000000827",
- },
- "timelineActivities": {
- "id": "00000000-0000-0000-0000-000000000828",
- },
- "trigger": {
- "id": "00000000-0000-0000-0000-000000000823",
- },
- "updatedAt": {
"id": "00000000-0000-0000-0000-000000000819",
},
- "updatedBy": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000831",
},
- "workflow": {
+ "deletedAt": {
+ "id": "00000000-0000-0000-0000-000000000821",
+ },
+ "id": {
+ "id": "00000000-0000-0000-0000-000000000818",
+ },
+ "name": {
"id": "00000000-0000-0000-0000-000000000822",
},
+ "position": {
+ "id": "00000000-0000-0000-0000-000000000826",
+ },
+ "runs": {
+ "id": "00000000-0000-0000-0000-000000000827",
+ },
+ "searchVector": {
+ "id": "00000000-0000-0000-0000-000000000830",
+ },
+ "status": {
+ "id": "00000000-0000-0000-0000-000000000825",
+ },
+ "steps": {
+ "id": "00000000-0000-0000-0000-000000000828",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000829",
+ },
+ "trigger": {
+ "id": "00000000-0000-0000-0000-000000000824",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000820",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000832",
+ },
+ "workflow": {
+ "id": "00000000-0000-0000-0000-000000000823",
+ },
},
- "id": "00000000-0000-0000-0000-000000000851",
+ "id": "00000000-0000-0000-0000-000000000852",
"views": {
"allWorkflowVersions": {
- "id": "00000000-0000-0000-0000-000000000837",
+ "id": "00000000-0000-0000-0000-000000000838",
"viewFieldGroups": {},
"viewFields": {
"name": {
- "id": "00000000-0000-0000-0000-000000000832",
+ "id": "00000000-0000-0000-0000-000000000833",
},
"runs": {
- "id": "00000000-0000-0000-0000-000000000836",
+ "id": "00000000-0000-0000-0000-000000000837",
},
"status": {
- "id": "00000000-0000-0000-0000-000000000834",
- },
- "updatedAt": {
"id": "00000000-0000-0000-0000-000000000835",
},
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000836",
+ },
"workflow": {
- "id": "00000000-0000-0000-0000-000000000833",
+ "id": "00000000-0000-0000-0000-000000000834",
},
},
"viewGroups": {},
},
"workflowVersionRecordPageFields": {
- "id": "00000000-0000-0000-0000-000000000850",
+ "id": "00000000-0000-0000-0000-000000000851",
"viewFieldGroups": {
"general": {
- "id": "00000000-0000-0000-0000-000000000848",
+ "id": "00000000-0000-0000-0000-000000000849",
},
"system": {
- "id": "00000000-0000-0000-0000-000000000849",
+ "id": "00000000-0000-0000-0000-000000000850",
},
},
"viewFields": {
"createdAt": {
- "id": "00000000-0000-0000-0000-000000000841",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000843",
- },
- "runs": {
- "id": "00000000-0000-0000-0000-000000000846",
- },
- "status": {
- "id": "00000000-0000-0000-0000-000000000838",
- },
- "steps": {
"id": "00000000-0000-0000-0000-000000000842",
},
- "timelineActivities": {
- "id": "00000000-0000-0000-0000-000000000847",
- },
- "trigger": {
- "id": "00000000-0000-0000-0000-000000000840",
- },
- "updatedAt": {
+ "createdBy": {
"id": "00000000-0000-0000-0000-000000000844",
},
- "updatedBy": {
+ "runs": {
+ "id": "00000000-0000-0000-0000-000000000847",
+ },
+ "status": {
+ "id": "00000000-0000-0000-0000-000000000839",
+ },
+ "steps": {
+ "id": "00000000-0000-0000-0000-000000000843",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000848",
+ },
+ "trigger": {
+ "id": "00000000-0000-0000-0000-000000000841",
+ },
+ "updatedAt": {
"id": "00000000-0000-0000-0000-000000000845",
},
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000846",
+ },
"workflow": {
- "id": "00000000-0000-0000-0000-000000000839",
+ "id": "00000000-0000-0000-0000-000000000840",
},
},
"viewGroups": {},
@@ -2854,126 +2857,126 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"workspaceMember": {
"fields": {
"accountOwnerForCompanies": {
- "id": "00000000-0000-0000-0000-000000000866",
- },
- "assignedTasks": {
- "id": "00000000-0000-0000-0000-000000000864",
- },
- "avatarUrl": {
- "id": "00000000-0000-0000-0000-000000000860",
- },
- "blocklist": {
- "id": "00000000-0000-0000-0000-000000000868",
- },
- "calendarEventParticipants": {
- "id": "00000000-0000-0000-0000-000000000869",
- },
- "calendarStartDay": {
- "id": "00000000-0000-0000-0000-000000000875",
- },
- "colorScheme": {
- "id": "00000000-0000-0000-0000-000000000858",
- },
- "createdAt": {
- "id": "00000000-0000-0000-0000-000000000853",
- },
- "createdBy": {
- "id": "00000000-0000-0000-0000-000000000877",
- },
- "dateFormat": {
- "id": "00000000-0000-0000-0000-000000000872",
- },
- "deletedAt": {
- "id": "00000000-0000-0000-0000-000000000855",
- },
- "id": {
- "id": "00000000-0000-0000-0000-000000000852",
- },
- "jobTitle": {
- "id": "00000000-0000-0000-0000-000000000862",
- },
- "locale": {
- "id": "00000000-0000-0000-0000-000000000859",
- },
- "messageParticipants": {
"id": "00000000-0000-0000-0000-000000000867",
},
- "name": {
- "id": "00000000-0000-0000-0000-000000000857",
- },
- "numberFormat": {
- "id": "00000000-0000-0000-0000-000000000876",
- },
- "ownedOpportunities": {
+ "assignedTasks": {
"id": "00000000-0000-0000-0000-000000000865",
},
- "position": {
- "id": "00000000-0000-0000-0000-000000000856",
- },
- "searchVector": {
- "id": "00000000-0000-0000-0000-000000000874",
- },
- "timeFormat": {
- "id": "00000000-0000-0000-0000-000000000873",
- },
- "timeZone": {
- "id": "00000000-0000-0000-0000-000000000871",
- },
- "timelineActivities": {
- "id": "00000000-0000-0000-0000-000000000870",
- },
- "updatedAt": {
- "id": "00000000-0000-0000-0000-000000000854",
- },
- "updatedBy": {
- "id": "00000000-0000-0000-0000-000000000878",
- },
- "userEmail": {
+ "avatarUrl": {
"id": "00000000-0000-0000-0000-000000000861",
},
- "userId": {
+ "blocklist": {
+ "id": "00000000-0000-0000-0000-000000000869",
+ },
+ "calendarEventParticipants": {
+ "id": "00000000-0000-0000-0000-000000000870",
+ },
+ "calendarStartDay": {
+ "id": "00000000-0000-0000-0000-000000000876",
+ },
+ "colorScheme": {
+ "id": "00000000-0000-0000-0000-000000000859",
+ },
+ "createdAt": {
+ "id": "00000000-0000-0000-0000-000000000854",
+ },
+ "createdBy": {
+ "id": "00000000-0000-0000-0000-000000000878",
+ },
+ "dateFormat": {
+ "id": "00000000-0000-0000-0000-000000000873",
+ },
+ "deletedAt": {
+ "id": "00000000-0000-0000-0000-000000000856",
+ },
+ "id": {
+ "id": "00000000-0000-0000-0000-000000000853",
+ },
+ "jobTitle": {
"id": "00000000-0000-0000-0000-000000000863",
},
+ "locale": {
+ "id": "00000000-0000-0000-0000-000000000860",
+ },
+ "messageParticipants": {
+ "id": "00000000-0000-0000-0000-000000000868",
+ },
+ "name": {
+ "id": "00000000-0000-0000-0000-000000000858",
+ },
+ "numberFormat": {
+ "id": "00000000-0000-0000-0000-000000000877",
+ },
+ "ownedOpportunities": {
+ "id": "00000000-0000-0000-0000-000000000866",
+ },
+ "position": {
+ "id": "00000000-0000-0000-0000-000000000857",
+ },
+ "searchVector": {
+ "id": "00000000-0000-0000-0000-000000000875",
+ },
+ "timeFormat": {
+ "id": "00000000-0000-0000-0000-000000000874",
+ },
+ "timeZone": {
+ "id": "00000000-0000-0000-0000-000000000872",
+ },
+ "timelineActivities": {
+ "id": "00000000-0000-0000-0000-000000000871",
+ },
+ "updatedAt": {
+ "id": "00000000-0000-0000-0000-000000000855",
+ },
+ "updatedBy": {
+ "id": "00000000-0000-0000-0000-000000000879",
+ },
+ "userEmail": {
+ "id": "00000000-0000-0000-0000-000000000862",
+ },
+ "userId": {
+ "id": "00000000-0000-0000-0000-000000000864",
+ },
},
- "id": "00000000-0000-0000-0000-000000000891",
+ "id": "00000000-0000-0000-0000-000000000892",
"views": {
"allWorkspaceMembers": {
- "id": "00000000-0000-0000-0000-000000000890",
+ "id": "00000000-0000-0000-0000-000000000891",
"viewFieldGroups": {},
"viewFields": {
"assignedTasks": {
- "id": "00000000-0000-0000-0000-000000000889",
+ "id": "00000000-0000-0000-0000-000000000890",
},
"avatarUrl": {
- "id": "00000000-0000-0000-0000-000000000881",
- },
- "colorScheme": {
"id": "00000000-0000-0000-0000-000000000882",
},
- "createdAt": {
- "id": "00000000-0000-0000-0000-000000000887",
- },
- "dateFormat": {
- "id": "00000000-0000-0000-0000-000000000885",
- },
- "locale": {
+ "colorScheme": {
"id": "00000000-0000-0000-0000-000000000883",
},
- "name": {
- "id": "00000000-0000-0000-0000-000000000879",
- },
- "ownedOpportunities": {
+ "createdAt": {
"id": "00000000-0000-0000-0000-000000000888",
},
- "timeFormat": {
+ "dateFormat": {
"id": "00000000-0000-0000-0000-000000000886",
},
- "timeZone": {
+ "locale": {
"id": "00000000-0000-0000-0000-000000000884",
},
- "userEmail": {
+ "name": {
"id": "00000000-0000-0000-0000-000000000880",
},
+ "ownedOpportunities": {
+ "id": "00000000-0000-0000-0000-000000000889",
+ },
+ "timeFormat": {
+ "id": "00000000-0000-0000-0000-000000000887",
+ },
+ "timeZone": {
+ "id": "00000000-0000-0000-0000-000000000885",
+ },
+ "userEmail": {
+ "id": "00000000-0000-0000-0000-000000000881",
+ },
},
"viewGroups": {},
},
diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-standard-flat-field-metadata.util.ts
index 4e4c41fa64..87894c5584 100644
--- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-standard-flat-field-metadata.util.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-standard-flat-field-metadata.util.ts
@@ -415,4 +415,24 @@ export const buildMessageStandardFlatFieldMetadatas = ({
twentyStandardApplicationId,
now,
}),
+ isDraft: createStandardFieldFlatMetadata({
+ objectName,
+ workspaceId,
+ context: {
+ fieldName: 'isDraft',
+ type: FieldMetadataType.BOOLEAN,
+ label: i18nLabel(msg`Is draft`),
+ description: i18nLabel(
+ msg`Whether this message is an unsent draft synced from the provider`,
+ ),
+ icon: 'IconPencil',
+ isNullable: false,
+ isUIEditable: false,
+ defaultValue: false,
+ },
+ standardObjectMetadataRelatedEntityIds,
+ dependencyFlatEntityMaps,
+ twentyStandardApplicationId,
+ now,
+ }),
});
diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts
index daf69615c7..dc5358f7a4 100644
--- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts
+++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts
@@ -29,6 +29,7 @@ const createMockMessage = (
messageCampaign: null,
messageCampaignId: null,
deliveryStatus: null,
+ isDraft: false,
deletedAt: null,
createdAt: '2024-03-20T09:00:00Z',
updatedAt: '2024-03-20T09:00:00Z',
diff --git a/packages/twenty-server/src/modules/messaging/common/standard-objects/message.workspace-entity.ts b/packages/twenty-server/src/modules/messaging/common/standard-objects/message.workspace-entity.ts
index 9a3f9f830a..ba57f0ab22 100644
--- a/packages/twenty-server/src/modules/messaging/common/standard-objects/message.workspace-entity.ts
+++ b/packages/twenty-server/src/modules/messaging/common/standard-objects/message.workspace-entity.ts
@@ -19,4 +19,5 @@ export class MessageWorkspaceEntity extends BaseWorkspaceEntity {
messageCampaign: EntityRelation | null;
messageCampaignId: string | null;
deliveryStatus: string | null;
+ isDraft: boolean;
}
diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/MESSAGING_FOLDER_MANAGER_ALWAYS_EXCLUDED_FOLDERS.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/MESSAGING_FOLDER_MANAGER_ALWAYS_EXCLUDED_FOLDERS.ts
index 3e70413fd1..b753f73670 100644
--- a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/MESSAGING_FOLDER_MANAGER_ALWAYS_EXCLUDED_FOLDERS.ts
+++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/MESSAGING_FOLDER_MANAGER_ALWAYS_EXCLUDED_FOLDERS.ts
@@ -1,7 +1,6 @@
import { StandardFolder } from 'src/modules/messaging/message-import-manager/drivers/types/standard-folder';
export const MESSAGING_FOLDER_MANAGER_ALWAYS_EXCLUDED_FOLDERS = [
- StandardFolder.DRAFTS,
StandardFolder.TRASH,
StandardFolder.JUNK,
];
diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/should-create-folder-by-default.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/should-create-folder-by-default.util.spec.ts
index b10370f793..52a01941e1 100644
--- a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/should-create-folder-by-default.util.spec.ts
+++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/should-create-folder-by-default.util.spec.ts
@@ -5,6 +5,7 @@ describe('shouldCreateFolderByDefault', () => {
it('should allow creating user folders', () => {
expect(shouldCreateFolderByDefault(StandardFolder.INBOX)).toBe(true);
expect(shouldCreateFolderByDefault(StandardFolder.SENT)).toBe(true);
+ expect(shouldCreateFolderByDefault(StandardFolder.DRAFTS)).toBe(true);
});
it('should allow creating custom folders', () => {
@@ -13,7 +14,6 @@ describe('shouldCreateFolderByDefault', () => {
});
it('should prevent creating system-excluded folders', () => {
- expect(shouldCreateFolderByDefault(StandardFolder.DRAFTS)).toBe(false);
expect(shouldCreateFolderByDefault(StandardFolder.TRASH)).toBe(false);
expect(shouldCreateFolderByDefault(StandardFolder.JUNK)).toBe(false);
});
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-excluded-system-labels.constant.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-excluded-system-labels.constant.ts
index 09e24112a4..719910c1c4 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-excluded-system-labels.constant.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-excluded-system-labels.constant.ts
@@ -1,6 +1 @@
-export const MESSAGING_GMAIL_EXCLUDED_SYSTEM_LABELS = [
- 'TRASH',
- 'SPAM',
- 'DRAFT',
- 'CHAT',
-];
+export const MESSAGING_GMAIL_EXCLUDED_SYSTEM_LABELS = ['TRASH', 'SPAM', 'CHAT'];
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/compute-gmail-exclude-search-filter.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/compute-gmail-exclude-search-filter.spec.ts
index a9ab3768a0..68e73ad192 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/compute-gmail-exclude-search-filter.spec.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/compute-gmail-exclude-search-filter.spec.ts
@@ -190,8 +190,8 @@ describe('computeGmailExcludeSearchFilter', () => {
expect(result).toContain('-label:trash');
expect(result).toContain('-label:spam');
- expect(result).toContain('-label:draft');
expect(result).toContain('-label:chat');
+ expect(result).not.toContain('-label:draft');
});
it('uses -category: syntax for category exclusions in ALL_FOLDERS mode', () => {
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/parse-and-format-gmail-message.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/parse-and-format-gmail-message.util.spec.ts
index f269acd972..94f8d765cf 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/parse-and-format-gmail-message.util.spec.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/parse-and-format-gmail-message.util.spec.ts
@@ -152,4 +152,32 @@ describe('parseAndFormatGmailMessage', () => {
expect(result).toBeNull();
});
+
+ it('should keep a draft missing a Message-ID header by synthesizing a fallback id', () => {
+ const result = parseAndFormatGmailMessage(
+ buildMessage(
+ [
+ { name: 'From', value: 'me@example.com' },
+ { name: 'To', value: 'alice@example.com' },
+ ],
+ { labelIds: ['DRAFT'] },
+ ),
+ connectedAccount,
+ );
+
+ expect(result?.isDraft).toBe(true);
+ expect(result?.headerMessageId).toBe('draft-msg-1');
+ });
+
+ it('should still drop a non-draft message missing a Message-ID header', () => {
+ const result = parseAndFormatGmailMessage(
+ buildMessage([
+ { name: 'From', value: 'sender@example.com' },
+ { name: 'To', value: 'alice@example.com' },
+ ]),
+ connectedAccount,
+ );
+
+ expect(result).toBeNull();
+ });
});
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts
index a7f30951f2..ff45f68336 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts
@@ -33,7 +33,18 @@ export const parseAndFormatGmailMessage = (
labelIds,
} = parseGmailMessage(message);
- if (!isDefined(from) || !isDefined(headerMessageId) || !isDefined(threadId)) {
+ const isDraft = (labelIds ?? []).includes('DRAFT');
+
+ // Gmail may omit the Message-ID header on drafts; synthesize a stable id from
+ // the message id so drafts aren't dropped.
+ const resolvedHeaderMessageId =
+ headerMessageId ?? (isDraft ? `draft-${id}` : undefined);
+
+ if (
+ !isDefined(from) ||
+ !isDefined(resolvedHeaderMessageId) ||
+ !isDefined(threadId)
+ ) {
return null;
}
@@ -58,13 +69,13 @@ export const parseAndFormatGmailMessage = (
(participant) => participant.role !== MessageParticipantRole.FROM,
);
- if (!hasRecipientParticipant) {
+ if (!hasRecipientParticipant && !isDraft) {
return null;
}
return {
externalId: id,
- headerMessageId,
+ headerMessageId: resolvedHeaderMessageId,
subject: subject || '',
messageThreadExternalId: threadId,
receivedAt: new Date(parseInt(internalDate)),
@@ -74,5 +85,6 @@ export const parseAndFormatGmailMessage = (
attachments,
messageFolderExternalIds: labelIds,
labelIds,
+ isDraft,
};
};
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts
index 78e0fb555c..c8138e2b59 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts
@@ -145,6 +145,7 @@ export class ImapGetMessagesService {
folderPath,
folderExternalId,
connectedAccount,
+ result.flags,
),
);
}
@@ -162,6 +163,7 @@ export class ImapGetMessagesService {
folderPath: string,
folderExternalId: string,
connectedAccount: Pick,
+ flags?: Set,
): MessageWithParticipants {
const fromAddresses = extractAddressesFromParsedEmail(parsed.from);
const senderAddress = fromAddresses[0]?.address ?? '';
@@ -184,6 +186,7 @@ export class ImapGetMessagesService {
})),
participants: extractParticipantsFromParsedEmail(parsed),
messageFolderExternalIds: [folderExternalId],
+ isDraft: flags?.has('\\Draft') ?? false,
};
}
}
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-parser.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-parser.service.ts
index 350645440f..6e8a90d75e 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-parser.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-parser.service.ts
@@ -6,6 +6,7 @@ import PostalMime, { type Email as ParsedEmail } from 'postal-mime';
export type MessageParseResult = {
uid: number;
parsed: ParsedEmail | null;
+ flags?: Set;
error?: Error;
};
@@ -40,7 +41,7 @@ export class ImapMessageParserService {
const messages = await client.fetchAll(
uidSet,
- { uid: true, source: true },
+ { uid: true, source: true, flags: true },
{ uid: true },
);
@@ -80,22 +81,22 @@ export class ImapMessageParserService {
private async parseMessage(
message: FetchMessageObject,
): Promise {
- const { uid, source } = message;
+ const { uid, source, flags } = message;
if (!source) {
this.logger.debug(`No source content for message UID ${uid}`);
- return { uid, parsed: null };
+ return { uid, parsed: null, flags };
}
try {
const parsed = await PostalMime.parse(source);
- return { uid, parsed };
+ return { uid, parsed, flags };
} catch (error) {
this.logger.error(`Failed to parse message UID ${uid}: ${error.message}`);
- return { uid, parsed: null, error: error as Error };
+ return { uid, parsed: null, flags, error: error as Error };
}
}
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/inbound-email/services/inbound-email-parser.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/inbound-email/services/inbound-email-parser.service.ts
index f7265027c7..02e4898701 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/inbound-email/services/inbound-email-parser.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/inbound-email/services/inbound-email-parser.service.ts
@@ -35,6 +35,7 @@ export class InboundEmailParserService {
direction: MessageDirection.INCOMING,
attachments: [],
participants: extractParticipantsFromParsedEmail(parsedEmail),
+ isDraft: false,
};
}
}
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.spec.ts
index d33add5c7b..426ccaccef 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.spec.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.spec.ts
@@ -99,6 +99,7 @@ describe('Microsoft get messages service', () => {
role: MessageParticipantRole.TO,
},
],
+ isDraft: false,
attachments: [],
messageFolderExternalIds: responseExample1.body.parentFolderId
? [responseExample1.body.parentFolderId]
@@ -145,6 +146,7 @@ describe('Microsoft get messages service', () => {
role: MessageParticipantRole.CC,
},
],
+ isDraft: false,
attachments: [],
messageFolderExternalIds: responseExample2.body.parentFolderId
? [responseExample2.body.parentFolderId]
@@ -188,6 +190,7 @@ describe('Microsoft get messages service', () => {
role: MessageParticipantRole.FROM,
},
],
+ isDraft: false,
attachments: [],
messageFolderExternalIds: responseExample.body.parentFolderId
? [responseExample.body.parentFolderId]
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts
index da4bc0f6ac..ff67a241e0 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts
@@ -163,6 +163,7 @@ export class MicrosoftGetMessagesService {
messageFolderExternalIds: response.parentFolderId
? [response.parentFolderId]
: [],
+ isDraft: response.isDraft ?? false,
};
});
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message.service.ts
index e71ce58120..59adb7343b 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message.service.ts
@@ -24,6 +24,7 @@ type MessageAccumulator = {
| 'receivedAt'
| 'text'
| 'messageThreadId'
+ | 'isDraft'
>;
threadToCreate?: Pick;
messageChannelMessageAssociationToCreate?: Pick<
@@ -168,6 +169,7 @@ export class MessagingMessageService {
receivedAt: message.receivedAt,
text: message.text,
messageThreadId,
+ isDraft: message.isDraft,
};
messageAccumulator.messageToCreate = messageToCreate;
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts
index 46bffe59aa..bdc879c8c9 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts
@@ -55,6 +55,7 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
text: 'Test content 1',
receivedAt: new Date(),
attachments: [],
+ isDraft: false,
messageThreadExternalId: 'thread-1',
direction: MessageDirection.OUTGOING,
participants: [
@@ -77,6 +78,7 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
text: 'Test content 2',
receivedAt: new Date(),
attachments: [],
+ isDraft: false,
messageThreadExternalId: 'thread-1',
direction: MessageDirection.INCOMING,
participants: [
@@ -329,4 +331,40 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
},
);
});
+
+ it('should not create contacts for unsent drafts', async () => {
+ await service.saveMessagesAndEnqueueContactCreation(
+ [
+ {
+ ...mockMessages[0],
+ isDraft: true,
+ participants: [
+ {
+ role: MessageParticipantRole.FROM,
+ handle: 'test@example.com',
+ displayName: 'Test User',
+ },
+ {
+ role: MessageParticipantRole.TO,
+ handle: 'prospect@company.com',
+ displayName: 'Prospect',
+ },
+ ],
+ },
+ ],
+ mockMessageChannel,
+ mockConnectedAccount,
+ workspaceId,
+ );
+
+ expect(messageQueueService.add).toHaveBeenCalledWith(
+ CreateCompanyAndContactJob.name,
+ {
+ workspaceId,
+ connectedAccount: mockConnectedAccount,
+ source: FieldActorSource.EMAIL,
+ contactsToCreate: [],
+ },
+ );
+ });
});
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts
index abbd649965..dc833e5e69 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts
@@ -96,7 +96,10 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
messageChannel.excludeNonProfessionalEmails &&
!isWorkEmail(participant.handle);
+ // Drafts are outgoing, so don't turn recipients of an
+ // unsent email into CRM contacts.
const shouldCreateContact =
+ !message.isDraft &&
!!participant.handle &&
!isParticipantConnectedAccount &&
!isExcludedByNonProfessionalEmails &&
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__mocks__/messages.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__mocks__/messages.ts
index 761a876235..2058b389fa 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__mocks__/messages.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__mocks__/messages.ts
@@ -25,6 +25,7 @@ export const messagingGetMessagesServiceGetMessages = [
},
],
attachments: [],
+ isDraft: false,
},
{
externalId: 'AA-work-emails-external',
@@ -47,6 +48,7 @@ export const messagingGetMessagesServiceGetMessages = [
},
],
attachments: [],
+ isDraft: false,
},
{
externalId: 'AA-personal-emails',
@@ -69,5 +71,6 @@ export const messagingGetMessagesServiceGetMessages = [
},
],
attachments: [],
+ isDraft: false,
},
] satisfies MessageWithParticipants[];
diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/filter-emails.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/filter-emails.util.spec.ts
index 62ccdcf477..bb7c941e1b 100644
--- a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/filter-emails.util.spec.ts
+++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/filter-emails.util.spec.ts
@@ -93,6 +93,7 @@ describe('filterEmails', () => {
},
],
attachments: [],
+ isDraft: false,
},
{
externalId: 'support-message',
@@ -110,6 +111,7 @@ describe('filterEmails', () => {
},
],
attachments: [],
+ isDraft: false,
},
{
externalId: 'regular-message',
@@ -127,6 +129,7 @@ describe('filterEmails', () => {
},
],
attachments: [],
+ isDraft: false,
},
];
@@ -155,6 +158,7 @@ describe('filterEmails', () => {
},
],
attachments: [],
+ isDraft: false,
},
];
@@ -176,6 +180,7 @@ describe('filterEmails', () => {
direction: MessageDirection.INCOMING,
participants: undefined as any,
attachments: [],
+ isDraft: false,
},
];
@@ -203,6 +208,7 @@ describe('filterEmails', () => {
},
],
attachments: [],
+ isDraft: false,
},
];
@@ -236,6 +242,7 @@ describe('filterEmails', () => {
},
],
attachments: [],
+ isDraft: false,
},
{
externalId: 'alias-sent-message',
@@ -258,6 +265,7 @@ describe('filterEmails', () => {
},
],
attachments: [],
+ isDraft: false,
},
{
externalId: 'reply-from-john',
@@ -280,6 +288,7 @@ describe('filterEmails', () => {
},
],
attachments: [],
+ isDraft: false,
},
{
externalId: 'incoming-from-noreply',
@@ -302,6 +311,7 @@ describe('filterEmails', () => {
},
],
attachments: [],
+ isDraft: false,
},
];
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/email-group/services/email-group-message-outbound.service.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/email-group/services/email-group-message-outbound.service.ts
index a847c5c148..d73d9bfadf 100644
--- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/email-group/services/email-group-message-outbound.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/email-group/services/email-group-message-outbound.service.ts
@@ -71,6 +71,13 @@ export class EmailGroupMessageOutboundService implements MessageOutboundDriver {
);
}
+ async sendDraft(): Promise {
+ throw new MessageChannelException(
+ 'Email handle channels do not support drafts.',
+ MessageChannelExceptionCode.INVALID_MESSAGE_CHANNEL_INPUT,
+ );
+ }
+
private async resolveEmailingDomain(
connectedAccount: ConnectedAccountEntity,
): Promise {
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/gmail/services/gmail-message-outbound.service.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/gmail/services/gmail-message-outbound.service.ts
index 201d2f3be9..c1f8f34387 100644
--- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/gmail/services/gmail-message-outbound.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/gmail/services/gmail-message-outbound.service.ts
@@ -1,4 +1,4 @@
-import { Injectable } from '@nestjs/common';
+import { Injectable, Logger } from '@nestjs/common';
import { isNonEmptyString } from '@sniptt/guards';
import { type gmail_v1, google } from 'googleapis';
@@ -17,6 +17,8 @@ import { toMailComposerOptions } from 'src/modules/messaging/message-outbound-ma
@Injectable()
export class GmailMessageOutboundService implements MessageOutboundDriver {
+ private readonly logger = new Logger(GmailMessageOutboundService.name);
+
constructor(
private readonly googleOAuth2ClientProvider: GoogleOAuth2ClientProvider,
) {}
@@ -67,6 +69,78 @@ export class GmailMessageOutboundService implements MessageOutboundDriver {
});
}
+ async sendDraft(
+ draftExternalId: string,
+ sendMessageInput: SendMessageInput,
+ connectedAccount: ConnectedAccountEntity,
+ ): Promise {
+ const sendResult = await this.sendMessage(
+ sendMessageInput,
+ connectedAccount,
+ );
+
+ try {
+ await this.deleteDraftByMessageId(connectedAccount, draftExternalId);
+ } catch (error) {
+ this.logger.warn(
+ `Failed to delete Gmail draft for message ${draftExternalId} after send: ${error}`,
+ );
+ }
+
+ return sendResult;
+ }
+
+ private async deleteDraftByMessageId(
+ connectedAccount: ConnectedAccountEntity,
+ messageId: string,
+ ): Promise {
+ const oAuth2Client = await this.googleOAuth2ClientProvider.getClient(
+ connectedAccount.id,
+ );
+
+ const gmailClient = google.gmail({ version: 'v1', auth: oAuth2Client });
+
+ const draftId = await this.findDraftIdByMessageId(gmailClient, messageId);
+
+ if (isDefined(draftId)) {
+ await gmailClient.users.drafts.delete({ userId: 'me', id: draftId });
+
+ return;
+ }
+
+ this.logger.warn(
+ `No Gmail draft found for message ${messageId}; skipping delete`,
+ );
+ }
+
+ private async findDraftIdByMessageId(
+ gmailClient: gmail_v1.Gmail,
+ messageId: string,
+ ): Promise {
+ let pageToken: string | undefined = undefined;
+
+ do {
+ const { data }: { data: gmail_v1.Schema$ListDraftsResponse } =
+ await gmailClient.users.drafts.list({
+ userId: 'me',
+ maxResults: 500,
+ pageToken,
+ });
+
+ const draft = (data.drafts ?? []).find(
+ (currentDraft) => currentDraft.message?.id === messageId,
+ );
+
+ if (isDefined(draft?.id)) {
+ return draft.id;
+ }
+
+ pageToken = data.nextPageToken ?? undefined;
+ } while (isDefined(pageToken));
+
+ return undefined;
+ }
+
private async composeGmailMessage(
connectedAccount: ConnectedAccountEntity,
sendMessageInput: SendMessageInput,
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service.ts
index e23042ae6c..13ff820410 100644
--- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service.ts
@@ -1,4 +1,4 @@
-import { Injectable } from '@nestjs/common';
+import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import MailComposer from 'nodemailer/lib/mail-composer';
@@ -13,6 +13,7 @@ import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connect
import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider';
import { ImapFindDraftsFolderService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-find-drafts-folder.service';
import { getImapFolderPath } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/get-imap-folder-path.util';
+import { parseMessageId } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-message-id.util';
import { SmtpClientProvider } from 'src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider';
import { type SendMessageInput } from 'src/modules/messaging/message-outbound-manager/types/send-message-input.type';
import { type SendMessageResult } from 'src/modules/messaging/message-outbound-manager/types/send-message-result.type';
@@ -21,6 +22,8 @@ import { toMailComposerOptions } from 'src/modules/messaging/message-outbound-ma
@Injectable()
export class ImapSmtpMessageOutboundService implements MessageOutboundDriver {
+ private readonly logger = new Logger(ImapSmtpMessageOutboundService.name);
+
constructor(
private readonly smtpClientProvider: SmtpClientProvider,
private readonly imapClientProvider: ImapClientProvider,
@@ -131,6 +134,56 @@ export class ImapSmtpMessageOutboundService implements MessageOutboundDriver {
}
}
+ async sendDraft(
+ draftExternalId: string,
+ sendMessageInput: SendMessageInput,
+ connectedAccount: ConnectedAccountEntity,
+ ): Promise {
+ const sendResult = await this.sendMessage(
+ sendMessageInput,
+ connectedAccount,
+ );
+
+ try {
+ await this.deleteDraft(draftExternalId, connectedAccount);
+ } catch (error) {
+ this.logger.warn(
+ `Failed to delete IMAP draft ${draftExternalId} after send: ${error}`,
+ );
+ }
+
+ return sendResult;
+ }
+
+ async deleteDraft(
+ externalId: string,
+ connectedAccount: ConnectedAccountEntity,
+ ): Promise {
+ const parsedMessageId = parseMessageId(externalId);
+
+ if (!isDefined(parsedMessageId)) {
+ throw new Error(
+ `Could not resolve IMAP drafts folder and uid from external id ${externalId}`,
+ );
+ }
+
+ const imapClient = await this.imapClientProvider.getClient(
+ connectedAccount.id,
+ );
+
+ try {
+ const lock = await imapClient.getMailboxLock(parsedMessageId.folder);
+
+ try {
+ await imapClient.messageDelete(`${parsedMessageId.uid}`, { uid: true });
+ } finally {
+ lock.release();
+ }
+ } finally {
+ await this.imapClientProvider.closeClient(imapClient);
+ }
+ }
+
private async compileRawMessage(
from: string,
sendMessageInput: SendMessageInput,
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/microsoft/services/microsoft-message-outbound.service.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/microsoft/services/microsoft-message-outbound.service.ts
index 5cf1a371e6..05cade5416 100644
--- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/microsoft/services/microsoft-message-outbound.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/microsoft/services/microsoft-message-outbound.service.ts
@@ -1,4 +1,4 @@
-import { Injectable } from '@nestjs/common';
+import { Injectable, Logger } from '@nestjs/common';
import { type MessageOutboundDriver } from 'src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface';
@@ -12,6 +12,8 @@ import { isDefined } from 'twenty-shared/utils';
@Injectable()
export class MicrosoftMessageOutboundService implements MessageOutboundDriver {
+ private readonly logger = new Logger(MicrosoftMessageOutboundService.name);
+
constructor(
private readonly microsoftOAuth2ClientProvider: MicrosoftOAuth2ClientProvider,
) {}
@@ -50,6 +52,32 @@ export class MicrosoftMessageOutboundService implements MessageOutboundDriver {
await this.createDraftMessage(microsoftClient, sendMessageInput);
}
+ async sendDraft(
+ draftExternalId: string,
+ sendMessageInput: SendMessageInput,
+ connectedAccount: ConnectedAccountEntity,
+ ): Promise {
+ const sendResult = await this.sendMessage(
+ sendMessageInput,
+ connectedAccount,
+ );
+
+ const microsoftClient = await this.microsoftOAuth2ClientProvider.getClient(
+ connectedAccount.id,
+ );
+
+ await microsoftClient
+ .api(`/me/messages/${draftExternalId}`)
+ .delete()
+ .catch((error) =>
+ this.logger.warn(
+ `Failed to delete Microsoft draft ${draftExternalId} after send: ${error}`,
+ ),
+ );
+
+ return sendResult;
+ }
+
private async createDraftMessage(
microsoftClient: MicrosoftGraphClient,
sendMessageInput: SendMessageInput,
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/dtos/send-email-output.dto.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/dtos/send-email-output.dto.ts
index 0a54a36ae1..786f823e30 100644
--- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/dtos/send-email-output.dto.ts
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/dtos/send-email-output.dto.ts
@@ -7,4 +7,7 @@ export class SendEmailOutputDTO {
@Field(() => String, { nullable: true })
error?: string;
+
+ @Field(() => String, { nullable: true })
+ messageThreadId?: string;
}
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/dtos/send-email.input.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/dtos/send-email.input.ts
index 5f11b875b1..5f24549c46 100644
--- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/dtos/send-email.input.ts
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/dtos/send-email.input.ts
@@ -32,6 +32,9 @@ export class SendEmailInput {
@Field(() => String, { nullable: true })
inReplyTo?: string;
+ @Field(() => String, { nullable: true })
+ draftMessageId?: string;
+
@Field(() => [SendEmailAttachmentInput], { nullable: true })
files?: SendEmailAttachmentInput[];
}
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface.ts
index b2355b1473..84e005b390 100644
--- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface.ts
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface.ts
@@ -12,4 +12,10 @@ export type MessageOutboundDriver = {
sendMessageInput: SendMessageInput,
connectedAccount: ConnectedAccountEntity,
): Promise;
+
+ sendDraft(
+ draftExternalId: string,
+ sendMessageInput: SendMessageInput,
+ connectedAccount: ConnectedAccountEntity,
+ ): Promise;
};
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/messaging-send-manager.module.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/messaging-send-manager.module.ts
index 7dcb1a4e24..74d166e565 100644
--- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/messaging-send-manager.module.ts
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/messaging-send-manager.module.ts
@@ -10,10 +10,12 @@ import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-
import { MessagingIMAPDriverModule } from 'src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module';
import { MessagingSmtpDriverModule } from 'src/modules/messaging/message-import-manager/drivers/smtp/messaging-smtp-driver.module';
import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module';
+import { MessagingMessageCleanerModule } from 'src/modules/messaging/message-cleaner/messaging-message-cleaner.module';
import { EmailGroupMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/drivers/email-group/services/email-group-message-outbound.service';
import { GmailMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/drivers/gmail/services/gmail-message-outbound.service';
import { ImapSmtpMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service';
import { MicrosoftMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/drivers/microsoft/services/microsoft-message-outbound.service';
+import { MessagingDraftSendService } from 'src/modules/messaging/message-outbound-manager/services/messaging-draft-send.service';
import { MessagingMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/services/messaging-message-outbound.service';
import { SendEmailService } from 'src/modules/messaging/message-outbound-manager/services/send-email.service';
import { SentMessagePersistenceService } from 'src/modules/messaging/message-outbound-manager/services/sent-message-persistence.service';
@@ -24,6 +26,7 @@ import { SentMessagePersistenceService } from 'src/modules/messaging/message-out
MessagingIMAPDriverModule,
MessagingSmtpDriverModule,
MessagingImportManagerModule,
+ MessagingMessageCleanerModule,
EmailingModule,
TypeOrmModule.forFeature([
MessageChannelEntity,
@@ -37,12 +40,14 @@ import { SentMessagePersistenceService } from 'src/modules/messaging/message-out
ImapSmtpMessageOutboundService,
EmailGroupMessageOutboundService,
MessagingMessageOutboundService,
+ MessagingDraftSendService,
SendEmailService,
SentMessagePersistenceService,
provideWorkspaceScopedRepository(EmailingDomainEntity),
],
exports: [
MessagingMessageOutboundService,
+ MessagingDraftSendService,
SendEmailService,
SentMessagePersistenceService,
],
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/resolvers/send-email.resolver.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/resolvers/send-email.resolver.ts
index 5ae14b2afe..91ba3de5fc 100644
--- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/resolvers/send-email.resolver.ts
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/resolvers/send-email.resolver.ts
@@ -23,6 +23,8 @@ import { ConnectedAccountMetadataService } from 'src/engine/metadata-modules/con
import { SendEmailOutputDTO } from 'src/modules/messaging/message-outbound-manager/dtos/send-email-output.dto';
import { SendEmailInput } from 'src/modules/messaging/message-outbound-manager/dtos/send-email.input';
import { SendEmailService } from 'src/modules/messaging/message-outbound-manager/services/send-email.service';
+import { isDefined } from 'twenty-shared/utils';
+import { isNonEmptyString } from '@sniptt/guards';
@MetadataResolver()
@UsePipes(ResolverValidationPipe)
@@ -79,26 +81,60 @@ export class SendEmailResolver {
const { data } = result;
- const sendResult = await this.sendEmailService.sendComposedEmail(data);
+ const sendResult = isDefined(input.draftMessageId)
+ ? await this.sendEmailService.sendComposedDraft(
+ data,
+ input.draftMessageId,
+ workspace.id,
+ )
+ : await this.sendEmailService.sendComposedEmail(data);
- if (data.shouldPersistMessage) {
- await this.sendEmailService.persistSentMessage(
- sendResult,
- data,
- workspace.id,
+ let messageThreadId: string | undefined;
+
+ try {
+ if (data.shouldPersistMessage) {
+ await this.sendEmailService.persistSentMessage(
+ sendResult,
+ data,
+ workspace.id,
+ );
+ }
+
+ if (isDefined(input.draftMessageId)) {
+ await this.sendEmailService.deleteSentDraft(
+ input.draftMessageId,
+ input.connectedAccountId,
+ workspace.id,
+ );
+ }
+
+ const sentMessageExternalId =
+ sendResult.messageExternalId ?? sendResult.headerMessageId;
+
+ messageThreadId =
+ isDefined(input.draftMessageId) &&
+ isNonEmptyString(sentMessageExternalId)
+ ? await this.sendEmailService.getSentMessageThreadId(
+ sentMessageExternalId,
+ workspace.id,
+ )
+ : undefined;
+
+ const attachmentFileIds = (input.files ?? []).map((file) => file.id);
+
+ if (attachmentFileIds.length > 0) {
+ await this.fileEmailAttachmentService.deleteFiles({
+ fileIds: attachmentFileIds,
+ workspaceId: workspace.id,
+ });
+ }
+ } catch (postSendError) {
+ this.logger.warn(
+ `Email sent but post-send cleanup failed (sync will recover): ${postSendError}`,
);
}
- const attachmentFileIds = (input.files ?? []).map((file) => file.id);
-
- if (attachmentFileIds.length > 0) {
- await this.fileEmailAttachmentService.deleteFiles({
- fileIds: attachmentFileIds,
- workspaceId: workspace.id,
- });
- }
-
- return { success: true };
+ return { success: true, messageThreadId };
} catch (error) {
if (error instanceof ForbiddenException) {
throw error;
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/messaging-draft-send.service.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/messaging-draft-send.service.ts
new file mode 100644
index 0000000000..65b2369781
--- /dev/null
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/messaging-draft-send.service.ts
@@ -0,0 +1,171 @@
+import { Injectable } from '@nestjs/common';
+
+import { isNonEmptyString } from '@sniptt/guards';
+import { isDefined } from 'twenty-shared/utils';
+import { In } from 'typeorm';
+
+import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
+import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
+import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
+import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
+import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
+import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service';
+import { MessagingMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/services/messaging-message-outbound.service';
+import { type SendMessageInput } from 'src/modules/messaging/message-outbound-manager/types/send-message-input.type';
+import { type SendMessageResult } from 'src/modules/messaging/message-outbound-manager/types/send-message-result.type';
+
+@Injectable()
+export class MessagingDraftSendService {
+ constructor(
+ private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
+ private readonly messageOutboundService: MessagingMessageOutboundService,
+ private readonly messageCleanerService: MessagingMessageCleanerService,
+ ) {}
+
+ async sendDraftMessage({
+ draftMessageId,
+ sendMessageInput,
+ connectedAccount,
+ workspaceId,
+ }: {
+ draftMessageId: string;
+ sendMessageInput: SendMessageInput;
+ connectedAccount: ConnectedAccountEntity;
+ workspaceId: string;
+ }): Promise {
+ const draftAssociation = await this.resolveDraftAssociation(
+ draftMessageId,
+ connectedAccount.id,
+ workspaceId,
+ );
+
+ if (!isDefined(draftAssociation)) {
+ throw new Error(
+ `Could not find a synced draft to send for message ${draftMessageId}`,
+ );
+ }
+
+ return this.messageOutboundService.sendDraft(
+ draftAssociation.messageExternalId,
+ sendMessageInput,
+ connectedAccount,
+ );
+ }
+
+ async getSentMessageThreadId({
+ messageExternalId,
+ workspaceId,
+ }: {
+ messageExternalId: string;
+ workspaceId: string;
+ }): Promise {
+ const authContext = buildSystemAuthContext(workspaceId);
+
+ return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
+ async () => {
+ const messageChannelMessageAssociationRepository =
+ await this.globalWorkspaceOrmManager.getRepository(
+ workspaceId,
+ 'messageChannelMessageAssociation',
+ );
+
+ const association =
+ await messageChannelMessageAssociationRepository.findOne({
+ where: { messageExternalId },
+ relations: ['message'],
+ });
+
+ return association?.message?.messageThreadId ?? undefined;
+ },
+ authContext,
+ { lite: true },
+ );
+ }
+
+ async deleteSentDraft({
+ draftMessageId,
+ connectedAccountId,
+ workspaceId,
+ }: {
+ draftMessageId: string;
+ connectedAccountId: string;
+ workspaceId: string;
+ }): Promise {
+ const draftAssociation = await this.resolveDraftAssociation(
+ draftMessageId,
+ connectedAccountId,
+ workspaceId,
+ );
+
+ if (!isDefined(draftAssociation)) {
+ return;
+ }
+
+ await this.messageCleanerService.deleteMessagesChannelMessageAssociationsAndRelatedOrphans(
+ {
+ workspaceId,
+ messageExternalIds: [draftAssociation.messageExternalId],
+ messageChannelId: draftAssociation.messageChannelId,
+ },
+ );
+ }
+
+ // Scoped to the caller's own channels so a member cannot act on another
+ // member's draft by passing its message id.
+ private async resolveDraftAssociation(
+ draftMessageId: string,
+ connectedAccountId: string,
+ workspaceId: string,
+ ): Promise<{ messageExternalId: string; messageChannelId: string } | null> {
+ const authContext = buildSystemAuthContext(workspaceId);
+
+ const associations =
+ await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
+ async () => {
+ const messageChannelRepository =
+ await this.globalWorkspaceOrmManager.getRepository(
+ workspaceId,
+ 'messageChannel',
+ );
+
+ const channels = await messageChannelRepository.find({
+ where: { connectedAccountId },
+ });
+
+ const channelIds = channels.map((channel) => channel.id);
+
+ if (channelIds.length === 0) {
+ return [];
+ }
+
+ const messageChannelMessageAssociationRepository =
+ await this.globalWorkspaceOrmManager.getRepository(
+ workspaceId,
+ 'messageChannelMessageAssociation',
+ );
+
+ return messageChannelMessageAssociationRepository.find({
+ where: {
+ messageId: draftMessageId,
+ messageChannelId: In(channelIds),
+ },
+ });
+ },
+ authContext,
+ { lite: true },
+ );
+
+ const association = associations.find((currentAssociation) =>
+ isNonEmptyString(currentAssociation.messageExternalId),
+ );
+
+ if (!association || !isNonEmptyString(association.messageExternalId)) {
+ return null;
+ }
+
+ return {
+ messageExternalId: association.messageExternalId,
+ messageChannelId: association.messageChannelId,
+ };
+ }
+}
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/messaging-message-outbound.service.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/messaging-message-outbound.service.ts
index e32629b63f..2ee0653102 100644
--- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/messaging-message-outbound.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/messaging-message-outbound.service.ts
@@ -93,4 +93,43 @@ export class MessagingMessageOutboundService {
);
}
}
+
+ public async sendDraft(
+ draftExternalId: string,
+ sendMessageInput: SendMessageInput,
+ connectedAccount: ConnectedAccountEntity,
+ ): Promise {
+ switch (connectedAccount.provider) {
+ case ConnectedAccountProvider.GOOGLE:
+ return this.gmailMessageOutboundService.sendDraft(
+ draftExternalId,
+ sendMessageInput,
+ connectedAccount,
+ );
+ case ConnectedAccountProvider.MICROSOFT:
+ return this.microsoftMessageOutboundService.sendDraft(
+ draftExternalId,
+ sendMessageInput,
+ connectedAccount,
+ );
+ case ConnectedAccountProvider.IMAP_SMTP_CALDAV:
+ return this.imapSmtpMessageOutboundService.sendDraft(
+ draftExternalId,
+ sendMessageInput,
+ connectedAccount,
+ );
+ case ConnectedAccountProvider.EMAIL_GROUP:
+ case ConnectedAccountProvider.OIDC:
+ case ConnectedAccountProvider.SAML:
+ case ConnectedAccountProvider.APP:
+ throw new Error(
+ `Provider ${connectedAccount.provider} does not support sending drafts`,
+ );
+ default:
+ assertUnreachable(
+ connectedAccount.provider,
+ `Provider ${connectedAccount.provider} not supported for sending drafts`,
+ );
+ }
+ }
}
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/send-email.service.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/send-email.service.ts
index a30ab4b042..2bfce450bf 100644
--- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/send-email.service.ts
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/send-email.service.ts
@@ -1,8 +1,10 @@
import { Injectable, Logger } from '@nestjs/common';
import { type ComposedEmail } from 'src/engine/core-modules/tool/tools/email-tool/types/composed-email.type';
+import { MessagingDraftSendService } from 'src/modules/messaging/message-outbound-manager/services/messaging-draft-send.service';
import { MessagingMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/services/messaging-message-outbound.service';
import { SentMessagePersistenceService } from 'src/modules/messaging/message-outbound-manager/services/sent-message-persistence.service';
+import { type SendMessageInput } from 'src/modules/messaging/message-outbound-manager/types/send-message-input.type';
import { type SendMessageResult } from 'src/modules/messaging/message-outbound-manager/types/send-message-result.type';
@Injectable()
@@ -11,27 +13,67 @@ export class SendEmailService {
constructor(
private readonly messageOutboundService: MessagingMessageOutboundService,
+ private readonly messagingDraftSendService: MessagingDraftSendService,
private readonly sentMessagePersistenceService: SentMessagePersistenceService,
) {}
async sendComposedEmail(data: ComposedEmail): Promise {
return this.messageOutboundService.sendMessage(
- {
- to: data.recipients.to,
- cc: data.recipients.cc.length > 0 ? data.recipients.cc : undefined,
- bcc: data.recipients.bcc.length > 0 ? data.recipients.bcc : undefined,
- subject: data.sanitizedSubject,
- body: data.plainTextBody,
- html: data.sanitizedHtmlBody,
- attachments: data.attachments,
- inReplyTo: data.inReplyTo,
- threadExternalId: data.threadExternalId,
- references: data.references,
- },
+ this.toSendMessageInput(data),
data.connectedAccount,
);
}
+ async sendComposedDraft(
+ data: ComposedEmail,
+ draftMessageId: string,
+ workspaceId: string,
+ ): Promise {
+ return this.messagingDraftSendService.sendDraftMessage({
+ draftMessageId,
+ sendMessageInput: this.toSendMessageInput(data),
+ connectedAccount: data.connectedAccount,
+ workspaceId,
+ });
+ }
+
+ async deleteSentDraft(
+ draftMessageId: string,
+ connectedAccountId: string,
+ workspaceId: string,
+ ): Promise {
+ await this.messagingDraftSendService.deleteSentDraft({
+ draftMessageId,
+ connectedAccountId,
+ workspaceId,
+ });
+ }
+
+ async getSentMessageThreadId(
+ messageExternalId: string,
+ workspaceId: string,
+ ): Promise {
+ return this.messagingDraftSendService.getSentMessageThreadId({
+ messageExternalId,
+ workspaceId,
+ });
+ }
+
+ private toSendMessageInput(data: ComposedEmail): SendMessageInput {
+ return {
+ to: data.recipients.to,
+ cc: data.recipients.cc.length > 0 ? data.recipients.cc : undefined,
+ bcc: data.recipients.bcc.length > 0 ? data.recipients.bcc : undefined,
+ subject: data.sanitizedSubject,
+ body: data.plainTextBody,
+ html: data.sanitizedHtmlBody,
+ attachments: data.attachments,
+ inReplyTo: data.inReplyTo,
+ threadExternalId: data.threadExternalId,
+ references: data.references,
+ };
+ }
+
async persistSentMessage(
sendResult: SendMessageResult,
data: ComposedEmail,
diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/utils/format-sent-message.util.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/utils/format-sent-message.util.ts
index 83150b6c46..0098b70f63 100644
--- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/utils/format-sent-message.util.ts
+++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/utils/format-sent-message.util.ts
@@ -55,5 +55,6 @@ export const formatSentMessage = (
direction: MessageDirection.OUTGOING,
attachments: [],
participants,
+ isDraft: false,
};
};
diff --git a/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts b/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts
index cc0c07d71b..eb497d4081 100644
--- a/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts
+++ b/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts
@@ -1480,6 +1480,9 @@ export const STANDARD_OBJECTS = {
deliveryStatus: {
universalIdentifier: '209254fa-2b89-429d-a72a-c401c4bd5a78',
},
+ isDraft: {
+ universalIdentifier: '20202020-4d3a-4b6e-9c1f-2a5e7b9d0c34',
+ },
createdBy: {
universalIdentifier: '6e52bde4-ed41-4462-aa70-121e496270b4',
},