Skip to content

Commit 4209b86

Browse files
mdvertolaguillim
authored andcommitted
twentyhq#12336 adding gmail email sync error handling (twentyhq#12383)
I believe that some emails with invalid characters are breaking the sync process. this PR attempts to create a "safeParseAddress" function. Hopefully this will change current behavior of a single email breaking the entire sync process to the sync process "skipping" an invalid email address and continuing on. I opened this because of issues explained in twentyhq#12336 --------- Co-authored-by: guillim <[email protected]>
1 parent 6cbd370 commit 4209b86

File tree

7 files changed

+140
-60
lines changed

7 files changed

+140
-60
lines changed

packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,22 @@ export const parseAndFormatGmailMessage = (
4040
return null;
4141
}
4242

43+
const toParticipants = to ?? deliveredTo;
44+
4345
const participants = [
44-
...formatAddressObjectAsParticipants(from, 'from'),
45-
...formatAddressObjectAsParticipants(to ?? deliveredTo, 'to'),
46-
...formatAddressObjectAsParticipants(cc, 'cc'),
47-
...formatAddressObjectAsParticipants(bcc, 'bcc'),
46+
...(from
47+
? formatAddressObjectAsParticipants([{ address: from }], 'from')
48+
: []),
49+
...(toParticipants
50+
? formatAddressObjectAsParticipants(
51+
[{ address: toParticipants, name: '' }],
52+
'to',
53+
)
54+
: []),
55+
...(cc ? formatAddressObjectAsParticipants([{ address: cc }], 'cc') : []),
56+
...(bcc
57+
? formatAddressObjectAsParticipants([{ address: bcc }], 'bcc')
58+
: []),
4859
];
4960

5061
const textWithoutReplyQuotations = text
@@ -57,7 +68,7 @@ export const parseAndFormatGmailMessage = (
5768
subject: subject || '',
5869
messageThreadExternalId: threadId,
5970
receivedAt: new Date(parseInt(internalDate)),
60-
direction: computeMessageDirection(from[0].address || '', connectedAccount),
71+
direction: computeMessageDirection(from || '', connectedAccount),
6172
participants,
6273
text: sanitizeString(textWithoutReplyQuotations),
6374
attachments,

packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-message.util.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import assert from 'assert';
22

3-
import addressparser from 'addressparser';
43
import { gmail_v1 } from 'googleapis';
54

65
import { getAttachmentData } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/get-attachment-data.util';
76
import { getBodyData } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/get-body-data.util';
87
import { getPropertyFromHeaders } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/get-property-from-headers.util';
8+
import { safeParseEmailAddressAddress } from 'src/modules/messaging/message-import-manager/utils/safe-parse.util';
99

1010
export const parseGmailMessage = (message: gmail_v1.Schema$Message) => {
1111
const subject = getPropertyFromHeaders(message, 'Subject');
@@ -36,11 +36,13 @@ export const parseGmailMessage = (message: gmail_v1.Schema$Message) => {
3636
historyId,
3737
internalDate,
3838
subject,
39-
from: rawFrom ? addressparser(rawFrom) : undefined,
40-
deliveredTo: rawDeliveredTo ? addressparser(rawDeliveredTo) : undefined,
41-
to: rawTo ? addressparser(rawTo) : undefined,
42-
cc: rawCc ? addressparser(rawCc) : undefined,
43-
bcc: rawBcc ? addressparser(rawBcc) : undefined,
39+
from: rawFrom ? safeParseEmailAddressAddress(rawFrom) : undefined,
40+
deliveredTo: rawDeliveredTo
41+
? safeParseEmailAddressAddress(rawDeliveredTo)
42+
: undefined,
43+
to: rawTo ? safeParseEmailAddressAddress(rawTo) : undefined,
44+
cc: rawCc ? safeParseEmailAddressAddress(rawCc) : undefined,
45+
bcc: rawBcc ? safeParseEmailAddressAddress(rawBcc) : undefined,
4446
text,
4547
attachments,
4648
};

packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Injectable, Logger } from '@nestjs/common';
22

3+
import { EmailAddress } from 'addressparser';
34
import { isDefined } from 'twenty-shared/utils';
45

56
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
@@ -9,6 +10,7 @@ import { MicrosoftImportDriverException } from 'src/modules/messaging/message-im
910
import { MicrosoftGraphBatchResponse } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.interface';
1011
import { MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
1112
import { formatAddressObjectAsParticipants } from 'src/modules/messaging/message-import-manager/utils/format-address-object-as-participants.util';
13+
import { safeParseEmailAddress } from 'src/modules/messaging/message-import-manager/utils/safe-parse.util';
1214

1315
import { MicrosoftFetchByBatchService } from './microsoft-fetch-by-batch.service';
1416
import { MicrosoftHandleErrorService } from './microsoft-handle-error.service';
@@ -78,38 +80,43 @@ export class MicrosoftGetMessagesService {
7880
);
7981
}
8082

83+
const safeParseFrom = response?.from?.emailAddress
84+
? [safeParseEmailAddress(response.from.emailAddress)]
85+
: [];
86+
87+
const safeParseTo = response?.toRecipients
88+
?.filter(isDefined)
89+
.map((recipient: { emailAddress: EmailAddress }) =>
90+
safeParseEmailAddress(recipient.emailAddress),
91+
);
92+
93+
const safeParseCc = response?.ccRecipients
94+
?.filter(isDefined)
95+
.map((recipient: { emailAddress: EmailAddress }) =>
96+
safeParseEmailAddress(recipient.emailAddress),
97+
);
98+
99+
const safeParseBcc = response?.bccRecipients
100+
?.filter(isDefined)
101+
.map((recipient: { emailAddress: EmailAddress }) =>
102+
safeParseEmailAddress(recipient.emailAddress),
103+
);
104+
81105
const participants = [
82-
...formatAddressObjectAsParticipants(
83-
response?.from?.emailAddress,
84-
'from',
85-
),
86-
...formatAddressObjectAsParticipants(
87-
response?.toRecipients
88-
?.filter(isDefined)
89-
// @ts-expect-error legacy noImplicitAny
90-
.map((recipient) => recipient.emailAddress),
91-
'to',
92-
),
93-
...formatAddressObjectAsParticipants(
94-
response?.ccRecipients
95-
?.filter(isDefined)
96-
// @ts-expect-error legacy noImplicitAny
97-
.map((recipient) => recipient.emailAddress),
98-
'cc',
99-
),
100-
...formatAddressObjectAsParticipants(
101-
response?.bccRecipients
102-
?.filter(isDefined)
103-
// @ts-expect-error legacy noImplicitAny
104-
.map((recipient) => recipient.emailAddress),
105-
'bcc',
106-
),
106+
...(safeParseFrom
107+
? formatAddressObjectAsParticipants(safeParseFrom, 'from')
108+
: []),
109+
...(safeParseTo
110+
? formatAddressObjectAsParticipants(safeParseTo, 'to')
111+
: []),
112+
...(safeParseCc
113+
? formatAddressObjectAsParticipants(safeParseCc, 'cc')
114+
: []),
115+
...(safeParseBcc
116+
? formatAddressObjectAsParticipants(safeParseBcc, 'bcc')
117+
: []),
107118
];
108119

109-
const safeParticipantsFormat = participants.filter((participant) => {
110-
return participant.handle.includes('@');
111-
});
112-
113120
return {
114121
externalId: response.id,
115122
subject: response.subject || '',
@@ -124,7 +131,7 @@ export class MicrosoftGetMessagesService {
124131
connectedAccount,
125132
)
126133
: MessageDirection.INCOMING,
127-
participants: safeParticipantsFormat,
134+
participants,
128135
attachments: [],
129136
};
130137
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export type EmailAddress = {
2+
address: string;
3+
name?: string;
4+
};

packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/format-address-object-as-participants.util.spec.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,42 @@ describe('formatAddressObjectAsParticipants', () => {
2323
]);
2424
});
2525

26-
it('should return an empty array if address object is undefined', () => {
27-
const addressObject = undefined;
26+
it('should return an empty array if address object handle has no @', () => {
27+
const addressObject = {
28+
name: 'John Doe',
29+
address: 'john.doe',
30+
};
2831

29-
const result = formatAddressObjectAsParticipants(addressObject, 'to');
32+
const result = formatAddressObjectAsParticipants([addressObject], 'to');
3033

3134
expect(result).toEqual([]);
3235
});
36+
37+
it('should return an empty array if address object handle is empty', () => {
38+
const addressObject = {
39+
name: 'John Doe',
40+
address: '',
41+
};
42+
43+
const result = formatAddressObjectAsParticipants([addressObject], 'to');
44+
45+
expect(result).toEqual([]);
46+
});
47+
48+
it('should return a lowewrcase handle if the handle is not lowercase', () => {
49+
const addressObject = {
50+
name: 'John Doe',
51+
address: '[email protected]',
52+
};
53+
54+
const result = formatAddressObjectAsParticipants([addressObject], 'to');
55+
56+
expect(result).toEqual([
57+
{
58+
role: 'to',
59+
handle: '[email protected]',
60+
displayName: 'John Doe',
61+
},
62+
]);
63+
});
3364
});
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,33 @@
1-
import addressparser from 'addressparser';
1+
import { isDefined } from 'twenty-shared/utils';
22

33
import { Participant } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message.type';
4-
5-
const formatAddressObjectAsArray = (
6-
addressObject: addressparser.EmailAddress | addressparser.EmailAddress[],
7-
): addressparser.EmailAddress[] => {
8-
return Array.isArray(addressObject) ? addressObject : [addressObject];
9-
};
4+
import { EmailAddress } from 'src/modules/messaging/message-import-manager/types/email-address';
105

116
const removeSpacesAndLowerCase = (email: string): string => {
127
return email.replace(/\s/g, '').toLowerCase();
138
};
149

1510
export const formatAddressObjectAsParticipants = (
16-
addressObject:
17-
| addressparser.EmailAddress
18-
| addressparser.EmailAddress[]
19-
| undefined,
11+
addressObjects: EmailAddress[],
2012
role: 'from' | 'to' | 'cc' | 'bcc',
2113
): Participant[] => {
22-
if (!addressObject) return [];
23-
const addressObjects = formatAddressObjectAsArray(addressObject);
24-
2514
const participants = addressObjects.map((addressObject) => {
2615
const address = addressObject.address;
2716

17+
if (!isDefined(address)) {
18+
return null;
19+
}
20+
21+
if (!address.includes('@')) {
22+
return null;
23+
}
24+
2825
return {
2926
role,
30-
handle: address ? removeSpacesAndLowerCase(address) : '',
27+
handle: removeSpacesAndLowerCase(address),
3128
displayName: addressObject.name || '',
3229
};
3330
});
3431

35-
return participants.flat();
32+
return participants.filter(isDefined) as Participant[];
3633
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Logger } from '@nestjs/common';
2+
3+
import addressparser from 'addressparser';
4+
5+
import { EmailAddress } from 'src/modules/messaging/message-import-manager/types/email-address';
6+
7+
export const safeParseEmailAddressAddress = (
8+
address: string,
9+
): string | undefined => {
10+
const logger = new Logger(safeParseEmailAddressAddress.name);
11+
12+
try {
13+
return addressparser(address)[0].address;
14+
} catch (error) {
15+
logger.error(`Error parsing address: ${address}`, error);
16+
17+
return undefined;
18+
}
19+
};
20+
21+
export const safeParseEmailAddress = (
22+
emailAddress: EmailAddress,
23+
): EmailAddress => {
24+
return {
25+
address: safeParseEmailAddressAddress(emailAddress.address) || '',
26+
name: emailAddress.name,
27+
};
28+
};

0 commit comments

Comments
 (0)