Skip to content

Commit 0218c5d

Browse files
UBERF-13118: Send emails from Huly mail threads (#9669)
Signed-off-by: Artem Savchenko <[email protected]> Signed-off-by: Artyom Savchenko <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 7d0034c commit 0218c5d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+778
-117
lines changed

common/config/rush/pnpm-lock.yaml

Lines changed: 21 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/scripts/docker.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ rush docker:build -p 20 \
1717
--to @hcengineering/pod-love \
1818
--to @hcengineering/pod-mail \
1919
--to @hcengineering/pod-datalake \
20-
--to @hcengineering/pod-inbound-mail \
20+
--to @hcengineering/pod-mail-worker \
2121
--to @hcengineering/pod-export \
2222
--to @hcengineering/pod-msg2file \
2323
--to @hcengineering/pod-media \

dev/docker-compose.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ services:
161161
- BRANDING_PATH=/var/cfg/branding.json
162162
# - DISABLE_SIGNUP=true
163163
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318/v1/traces
164+
- MAILBOX_DOMAINS=huly.dev.local
165+
- MAILBOX_MAX_COUNT_PER_ACCOUNT=2
164166
restart: unless-stopped
165167
stats:
166168
image: hardcoreeng/stats

rush.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2509,8 +2509,8 @@
25092509
"shouldPublish": false
25102510
},
25112511
{
2512-
"packageName": "@hcengineering/pod-inbound-mail",
2513-
"projectFolder": "services/mail/pod-inbound-mail",
2512+
"packageName": "@hcengineering/pod-mail-worker",
2513+
"projectFolder": "services/mail/pod-mail-worker",
25142514
"shouldPublish": false
25152515
},
25162516
{

server/account/src/serviceOperations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ export async function getPersonInfo (
555555
): Promise<PersonInfo> {
556556
const { account } = params
557557
const { extra } = decodeTokenVerbose(ctx, token)
558-
verifyAllowedServices(['workspace', 'tool', 'gmail'], extra)
558+
verifyAllowedServices(['workspace', 'tool', 'gmail', 'huly-mail'], extra)
559559

560560
if (account == null || account === '') {
561561
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
@@ -917,7 +917,7 @@ export async function findFullSocialIds (
917917
): Promise<SocialId[]> {
918918
const { socialIds } = params
919919
const { extra } = decodeTokenVerbose(ctx, token)
920-
verifyAllowedServices(['gmail', 'tool', 'workspace'], extra)
920+
verifyAllowedServices(['gmail', 'tool', 'workspace', 'huly-mail'], extra)
921921

922922
if (socialIds == null || socialIds.length === 0) {
923923
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))

server/account/src/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1746,7 +1746,8 @@ export const integrationServices = [
17461746
'mailbox',
17471747
'caldav',
17481748
'gmail',
1749-
'google-calendar'
1749+
'google-calendar',
1750+
'huly-mail'
17501751
]
17511752

17521753
export async function findExistingIntegration (

services/gmail/pod-gmail/src/__tests__/gmailClient.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ jest.mock('@hcengineering/core', () => {
7474
jest.mock('@hcengineering/mail-common', () => ({
7575
createMessages: jest.fn().mockResolvedValue(undefined),
7676
getChannel: jest.fn().mockResolvedValue({ _id: 'test-channel-id' }),
77-
isSyncedMessage: jest.fn().mockReturnValue(false)
77+
isSyncedMessage: jest.fn().mockReturnValue(false),
78+
getMessageExtra: jest.fn().mockReturnValue({}),
79+
getMailHeaders: jest.fn().mockReturnValue([]),
80+
isHulyMessage: jest.fn().mockReturnValue(false)
7881
}))
7982

8083
jest.mock('googleapis', () => ({

services/gmail/pod-gmail/src/gmail.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,20 @@ import {
3535
isWorkspaceLoginInfo,
3636
AccountClient
3737
} from '@hcengineering/account-client'
38-
import { MailRecipient, type SyncOptions, getChannel, isSyncedMessage } from '@hcengineering/mail-common'
38+
import {
39+
MailRecipient,
40+
type SyncOptions,
41+
getChannel,
42+
getMailHeaders,
43+
isSyncedMessage
44+
} from '@hcengineering/mail-common'
3945
import chat from '@hcengineering/chat'
4046

4147
import { encode64 } from './base64'
4248
import config from './config'
4349
import { GmailController } from './gmailController'
4450
import { RateLimiter } from './rateLimiter'
45-
import {
46-
type ProjectCredentials,
47-
type Token,
48-
type User,
49-
type SyncState,
50-
HulyMailHeader,
51-
HulyMessageIdHeader
52-
} from './types'
51+
import { type ProjectCredentials, type Token, type User, type SyncState, GmailMessageType } from './types'
5352
import { addFooter, isToken, serviceToken, getKvsClient, createGmailSearchQuery } from './utils'
5453
import type { WorkspaceClient } from './workspaceClient'
5554
import { getOrCreateSocialId } from './accounts'
@@ -73,8 +72,7 @@ function makeHTMLBody (message: NewMessage, from: string): string {
7372
'Content-Transfer-Encoding: 7bit\n',
7473
`To: ${message.to} \n`,
7574
`From: ${from} \n`,
76-
`${HulyMailHeader}: true\n`,
77-
`${HulyMessageIdHeader}: ${message._id}\n`
75+
...getMailHeaders(GmailMessageType, message._id)
7876
]
7977

8078
if (message.replyTo != null) {
@@ -385,7 +383,7 @@ export class GmailClient {
385383
}
386384

387385
this.ctx.info('Sending gmail message', { id: message._id, email })
388-
const gmailBody = await makeHTMLBodyV2(this.accountClient, message, thread, this.socialId._id, email)
386+
const gmailBody = await makeHTMLBodyV2(this.ctx, this.accountClient, message, thread, this.socialId._id, email)
389387
await this.rateLimiter.take(100)
390388
await this.gmail.messages.send({
391389
userId: 'me',

services/gmail/pod-gmail/src/message/v2/message.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ import {
2424
EmailMessage,
2525
getProducer,
2626
MailRecipient,
27-
getMessageExtra
27+
getMessageExtra,
28+
HulyMailHeader,
29+
HulyMessageIdHeader
2830
} from '@hcengineering/mail-common'
2931
import { type KeyValueClient } from '@hcengineering/kvs-client'
3032
import { AccountClient, isWorkspaceLoginInfo, WorkspaceLoginInfo } from '@hcengineering/account-client'
@@ -33,7 +35,7 @@ import { IMessageManager } from '../types'
3335
import config from '../../config'
3436
import { AttachmentHandler } from '../attachments'
3537
import { decode64 } from '../../base64'
36-
import { GmailMessageType, HulyMailHeader, HulyMessageIdHeader } from '../../types'
38+
import { GmailMessageType } from '../../types'
3739

3840
export class MessageManagerV2 implements IMessageManager {
3941
private wsInfo: WorkspaceLoginInfo | undefined = undefined

services/gmail/pod-gmail/src/message/v2/send.ts

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,42 @@
11
import { CreateMessageEvent } from '@hcengineering/communication-sdk-types'
22
import { type GaxiosResponse } from 'gaxios'
33
import { gmail_v1 } from 'googleapis'
4-
import { markdownToHtml, getReplySubject } from '@hcengineering/mail-common'
4+
import {
5+
markdownToHtml,
6+
getReplySubject,
7+
getRecipients,
8+
getMailHeaders,
9+
HulyMailHeader,
10+
HulyMessageIdHeader
11+
} from '@hcengineering/mail-common'
12+
import { Card } from '@hcengineering/card'
13+
import { MeasureContext, PersonId } from '@hcengineering/core'
14+
import { AccountClient } from '@hcengineering/account-client'
515

616
import { encode64 } from '../../base64'
717
import { addFooter } from '../../utils'
8-
import { Card } from '@hcengineering/card'
9-
import { PersonId, SocialIdType } from '@hcengineering/core'
10-
import { AccountClient } from '@hcengineering/account-client'
11-
import { HulyMailHeader, HulyMessageIdHeader } from '../../types'
18+
import { GmailMessageType } from '../../types'
1219

1320
export async function makeHTMLBodyV2 (
21+
ctx: MeasureContext,
1422
accountClient: AccountClient,
1523
message: CreateMessageEvent,
1624
thread: Card,
1725
personId: PersonId,
1826
from: string
1927
): Promise<string | undefined> {
20-
const collaborators: PersonId[] = (thread as any).members ?? []
21-
if (collaborators.length === 0) {
28+
const recipients = await getRecipients(ctx, accountClient, thread, personId)
29+
if (recipients === undefined) {
2230
return undefined
2331
}
24-
const recipients = collaborators.length > 1 ? collaborators.filter((c) => c !== personId) : collaborators
25-
const mailSocialIds = (await accountClient.findFullSocialIds(recipients)).filter(
26-
(id) => id.type === SocialIdType.EMAIL
27-
)
28-
if (mailSocialIds.length === 0) {
29-
console.warn('No social IDs found for recipients', { recipients })
30-
return undefined
31-
}
32-
const to = mailSocialIds[0].value
33-
const copy = mailSocialIds.length > 1 ? mailSocialIds.slice(1).map((s) => s.value) : []
34-
32+
const { to, copy } = recipients
3533
const str = [
3634
'Content-Type: text/html; charset="UTF-8"\n',
3735
'MIME-Version: 1.0\n',
3836
'Content-Transfer-Encoding: 7bit\n',
3937
`To: ${to} \n`,
4038
`From: ${from} \n`,
41-
`${HulyMailHeader}: true\n`,
42-
`${HulyMessageIdHeader}: ${message._id}\n`
39+
...getMailHeaders(GmailMessageType, message._id)
4340
]
4441

4542
// TODO: get reply-to from channel

0 commit comments

Comments
 (0)