feat: SMTP Driver Integration (#12993)
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@ -14,7 +14,7 @@ FRONTEND_URL=http://localhost:3001
|
||||
# REFRESH_TOKEN_EXPIRES_IN=90d
|
||||
# FILE_TOKEN_EXPIRES_IN=1d
|
||||
# MESSAGING_PROVIDER_GMAIL_ENABLED=false
|
||||
# MESSAGING_PROVIDER_IMAP_ENABLED=false
|
||||
# IS_IMAP_SMTP_CALDAV_ENABLED=false
|
||||
# CALENDAR_PROVIDER_GOOGLE_ENABLED=false
|
||||
# MESSAGING_PROVIDER_MICROSOFT_ENABLED=false
|
||||
# CALENDAR_PROVIDER_MICROSOFT_ENABLED=false
|
||||
|
||||
@ -11,7 +11,7 @@ FRONTEND_URL=http://localhost:3001
|
||||
|
||||
AUTH_GOOGLE_ENABLED=false
|
||||
MESSAGING_PROVIDER_GMAIL_ENABLED=false
|
||||
MESSAGING_PROVIDER_IMAP_ENABLED=false
|
||||
IS_IMAP_SMTP_CALDAV_ENABLED=false
|
||||
CALENDAR_PROVIDER_GOOGLE_ENABLED=false
|
||||
MESSAGING_PROVIDER_MICROSOFT_ENABLED=false
|
||||
CALENDAR_PROVIDER_MICROSOFT_ENABLED=false
|
||||
|
||||
@ -96,7 +96,7 @@ describe('ClientConfigController', () => {
|
||||
isGoogleMessagingEnabled: false,
|
||||
isGoogleCalendarEnabled: false,
|
||||
isConfigVariablesInDbEnabled: false,
|
||||
isIMAPMessagingEnabled: false,
|
||||
isImapSmtpCaldavEnabled: false,
|
||||
calendarBookingPageId: undefined,
|
||||
};
|
||||
|
||||
|
||||
@ -178,7 +178,7 @@ export class ClientConfig {
|
||||
isConfigVariablesInDbEnabled: boolean;
|
||||
|
||||
@Field(() => Boolean)
|
||||
isIMAPMessagingEnabled: boolean;
|
||||
isImapSmtpCaldavEnabled: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
calendarBookingPageId?: string;
|
||||
|
||||
@ -138,8 +138,8 @@ export class ClientConfigService {
|
||||
isConfigVariablesInDbEnabled: this.twentyConfigService.get(
|
||||
'IS_CONFIG_VARIABLES_IN_DB_ENABLED',
|
||||
),
|
||||
isIMAPMessagingEnabled: this.twentyConfigService.get(
|
||||
'MESSAGING_PROVIDER_IMAP_ENABLED',
|
||||
isImapSmtpCaldavEnabled: this.twentyConfigService.get(
|
||||
'IS_IMAP_SMTP_CALDAV_ENABLED',
|
||||
),
|
||||
calendarBookingPageId: this.twentyConfigService.get(
|
||||
'CALENDAR_BOOKING_PAGE_ID',
|
||||
|
||||
@ -13,12 +13,13 @@ export type PublicFeatureFlag = {
|
||||
|
||||
export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
|
||||
{
|
||||
key: FeatureFlagKey.IS_IMAP_ENABLED,
|
||||
key: FeatureFlagKey.IS_IMAP_SMTP_CALDAV_ENABLED,
|
||||
metadata: {
|
||||
label: 'IMAP',
|
||||
label: 'IMAP, SMTP, CalDAV',
|
||||
description:
|
||||
'Easily add email accounts from any provider that supports IMAP (and soon, send emails with SMTP)',
|
||||
imagePath: 'https://twenty.com/images/lab/is-imap-enabled.png',
|
||||
'Easily add email accounts from any provider that supports IMAP, send emails with SMTP (and soon, sync calendars with CalDAV)',
|
||||
imagePath:
|
||||
'https://twenty.com/images/lab/is-imap-smtp-caldav-enabled.png',
|
||||
},
|
||||
},
|
||||
...(process.env.CLOUDFLARE_API_KEY
|
||||
|
||||
@ -5,7 +5,7 @@ export enum FeatureFlagKey {
|
||||
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
IS_AI_ENABLED = 'IS_AI_ENABLED',
|
||||
IS_IMAP_ENABLED = 'IS_IMAP_ENABLED',
|
||||
IS_IMAP_SMTP_CALDAV_ENABLED = 'IS_IMAP_SMTP_CALDAV_ENABLED',
|
||||
IS_MORPH_RELATION_ENABLED = 'IS_MORPH_RELATION_ENABLED',
|
||||
IS_WORKFLOW_FILTERING_ENABLED = 'IS_WORKFLOW_FILTERING_ENABLED',
|
||||
IS_RELATION_CONNECT_ENABLED = 'IS_RELATION_CONNECT_ENABLED',
|
||||
|
||||
@ -14,9 +14,6 @@ export class ConnectionParameters {
|
||||
@Field(() => Number)
|
||||
port: number;
|
||||
|
||||
@Field(() => String)
|
||||
username: string;
|
||||
|
||||
/**
|
||||
* Note: This field is stored in plain text in the database.
|
||||
* While encrypting it could provide an extra layer of defense, we have decided not to,
|
||||
@ -37,9 +34,6 @@ export class ConnectionParametersOutput {
|
||||
@Field(() => Number)
|
||||
port: number;
|
||||
|
||||
@Field(() => String)
|
||||
username: string;
|
||||
|
||||
@Field(() => String)
|
||||
password: string;
|
||||
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
|
||||
import {
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
UseFilters,
|
||||
UseGuards,
|
||||
UsePipes,
|
||||
} from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
@ -39,36 +45,6 @@ export class ImapSmtpCaldavResolver {
|
||||
private readonly mailConnectionValidatorService: ImapSmtpCaldavValidatorService,
|
||||
) {}
|
||||
|
||||
private async checkIfFeatureEnabled(
|
||||
workspaceId: string,
|
||||
accountType: AccountType,
|
||||
): Promise<void> {
|
||||
if (accountType.type === 'IMAP') {
|
||||
const isImapEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_IMAP_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!isImapEnabled) {
|
||||
throw new UserInputError(
|
||||
'IMAP feature is not enabled for this workspace',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (accountType.type === 'SMTP') {
|
||||
throw new UserInputError(
|
||||
'SMTP feature is not enabled for this workspace',
|
||||
);
|
||||
}
|
||||
|
||||
if (accountType.type === 'CALDAV') {
|
||||
throw new UserInputError(
|
||||
'CALDAV feature is not enabled for this workspace',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => ConnectedImapSmtpCaldavAccount)
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
async getConnectedImapSmtpCaldavAccount(
|
||||
@ -111,7 +87,18 @@ export class ImapSmtpCaldavResolver {
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args('id', { nullable: true }) id?: string,
|
||||
): Promise<ImapSmtpCaldavConnectionSuccess> {
|
||||
await this.checkIfFeatureEnabled(workspace.id, accountType);
|
||||
const isImapSmtpCaldavFeatureFlagEnabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_IMAP_SMTP_CALDAV_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (!isImapSmtpCaldavFeatureFlagEnabled) {
|
||||
throw new HttpException(
|
||||
'IMAP, SMTP, CalDAV feature is not enabled for this workspace',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
|
||||
const validatedParams =
|
||||
this.mailConnectionValidatorService.validateProtocolConnectionParams(
|
||||
@ -119,6 +106,7 @@ export class ImapSmtpCaldavResolver {
|
||||
);
|
||||
|
||||
await this.ImapSmtpCaldavConnectionService.testImapSmtpCaldav(
|
||||
handle,
|
||||
validatedParams,
|
||||
accountType.type,
|
||||
);
|
||||
|
||||
@ -10,7 +10,6 @@ export class ImapSmtpCaldavValidatorService {
|
||||
private readonly protocolConnectionSchema = z.object({
|
||||
host: z.string().min(1, 'Host is required'),
|
||||
port: z.number().int().positive('Port must be a positive number'),
|
||||
username: z.string().min(1, 'Username is required'),
|
||||
password: z.string().min(1, 'Password is required'),
|
||||
secure: z.boolean().optional(),
|
||||
});
|
||||
@ -19,7 +18,10 @@ export class ImapSmtpCaldavValidatorService {
|
||||
params: ConnectionParameters,
|
||||
): ConnectionParameters {
|
||||
if (!params) {
|
||||
throw new UserInputError('Protocol connection parameters are required');
|
||||
throw new UserInputError('Protocol connection parameters are required', {
|
||||
userFriendlyMessage:
|
||||
'Please provide connection details to configure your email account.',
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
@ -32,10 +34,17 @@ export class ImapSmtpCaldavValidatorService {
|
||||
|
||||
throw new UserInputError(
|
||||
`Protocol connection validation failed: ${errorMessages}`,
|
||||
{
|
||||
userFriendlyMessage:
|
||||
'Please check your connection settings. Make sure the server host, port, and password are correct.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
throw new UserInputError('Protocol connection validation failed');
|
||||
throw new UserInputError('Protocol connection validation failed', {
|
||||
userFriendlyMessage:
|
||||
'There was an issue with your connection settings. Please try again.',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { ImapFlow } from 'imapflow';
|
||||
import { createTransport } from 'nodemailer';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
|
||||
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
@ -19,17 +20,16 @@ export class ImapSmtpCaldavService {
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async testImapConnection(params: ConnectionParameters): Promise<boolean> {
|
||||
if (!params.host || !params.username || !params.password) {
|
||||
throw new UserInputError('Missing required IMAP connection parameters');
|
||||
}
|
||||
|
||||
async testImapConnection(
|
||||
handle: string,
|
||||
params: ConnectionParameters,
|
||||
): Promise<boolean> {
|
||||
const client = new ImapFlow({
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
secure: params.secure ?? true,
|
||||
auth: {
|
||||
user: params.username,
|
||||
user: handle,
|
||||
pass: params.password,
|
||||
},
|
||||
logger: false,
|
||||
@ -57,16 +57,27 @@ export class ImapSmtpCaldavService {
|
||||
if (error.authenticationFailed) {
|
||||
throw new UserInputError(
|
||||
'IMAP authentication failed. Please check your credentials.',
|
||||
{
|
||||
userFriendlyMessage:
|
||||
"We couldn't log in to your email account. Please check your email address and password, then try again.",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (error.code === 'ECONNREFUSED') {
|
||||
throw new UserInputError(
|
||||
`IMAP connection refused. Please verify server and port.`,
|
||||
{
|
||||
userFriendlyMessage:
|
||||
"We couldn't connect to your email server. Please check your server settings and try again.",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
throw new UserInputError(`IMAP connection failed: ${error.message}`);
|
||||
throw new UserInputError(`IMAP connection failed: ${error.message}`, {
|
||||
userFriendlyMessage:
|
||||
'We encountered an issue connecting to your email account. Please check your settings and try again.',
|
||||
});
|
||||
} finally {
|
||||
if (client.authenticated) {
|
||||
await client.logout();
|
||||
@ -74,36 +85,70 @@ export class ImapSmtpCaldavService {
|
||||
}
|
||||
}
|
||||
|
||||
async testSmtpConnection(params: ConnectionParameters): Promise<boolean> {
|
||||
this.logger.log('SMTP connection testing not yet implemented', params);
|
||||
async testSmtpConnection(
|
||||
handle: string,
|
||||
params: ConnectionParameters,
|
||||
): Promise<boolean> {
|
||||
const transport = createTransport({
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
auth: {
|
||||
user: handle,
|
||||
pass: params.password,
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await transport.verify();
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`SMTP connection failed: ${error.message}`,
|
||||
error.stack,
|
||||
);
|
||||
throw new UserInputError(`SMTP connection failed: ${error.message}`, {
|
||||
userFriendlyMessage:
|
||||
"We couldn't connect to your outgoing email server. Please check your SMTP settings and try again.",
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async testCaldavConnection(params: ConnectionParameters): Promise<boolean> {
|
||||
async testCaldavConnection(
|
||||
handle: string,
|
||||
params: ConnectionParameters,
|
||||
): Promise<boolean> {
|
||||
this.logger.log('CALDAV connection testing not yet implemented', params);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async testImapSmtpCaldav(
|
||||
handle: string,
|
||||
params: ConnectionParameters,
|
||||
accountType: AccountType,
|
||||
): Promise<boolean> {
|
||||
if (accountType === 'IMAP') {
|
||||
return this.testImapConnection(params);
|
||||
return this.testImapConnection(handle, params);
|
||||
}
|
||||
|
||||
if (accountType === 'SMTP') {
|
||||
return this.testSmtpConnection(params);
|
||||
return this.testSmtpConnection(handle, params);
|
||||
}
|
||||
|
||||
if (accountType === 'CALDAV') {
|
||||
return this.testCaldavConnection(params);
|
||||
return this.testCaldavConnection(handle, params);
|
||||
}
|
||||
|
||||
throw new UserInputError(
|
||||
'Invalid account type. Must be one of: IMAP, SMTP, CALDAV',
|
||||
{
|
||||
userFriendlyMessage:
|
||||
'Please select a valid connection type (IMAP, SMTP, or CalDAV) and try again.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
export type ConnectionParameters = {
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
secure?: boolean;
|
||||
};
|
||||
@ -9,7 +8,6 @@ export type ConnectionParameters = {
|
||||
export type AccountType = 'IMAP' | 'SMTP' | 'CALDAV';
|
||||
|
||||
export type ImapSmtpCaldavParams = {
|
||||
handle: string;
|
||||
IMAP?: ConnectionParameters;
|
||||
SMTP?: ConnectionParameters;
|
||||
CALDAV?: ConnectionParameters;
|
||||
|
||||
@ -148,7 +148,7 @@ export class ConfigVariables {
|
||||
description: 'Enable or disable the IMAP messaging integration',
|
||||
type: ConfigVariableType.BOOLEAN,
|
||||
})
|
||||
MESSAGING_PROVIDER_IMAP_ENABLED = false;
|
||||
IS_IMAP_SMTP_CALDAV_ENABLED = false;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.MicrosoftAuth,
|
||||
|
||||
@ -46,7 +46,7 @@ export const seedFeatureFlags = async (
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_IMAP_ENABLED,
|
||||
key: FeatureFlagKey.IS_IMAP_SMTP_CALDAV_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
|
||||
@ -88,6 +88,20 @@ export class ImapSmtpCalDavAPIService {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
let shouldEnableSync = false;
|
||||
|
||||
if (connectedAccount) {
|
||||
const hadOnlySmtp =
|
||||
connectedAccount.connectionParameters?.SMTP &&
|
||||
!connectedAccount.connectionParameters?.IMAP &&
|
||||
!connectedAccount.connectionParameters?.CALDAV;
|
||||
|
||||
const isAddingImapOrCaldav =
|
||||
input.accountType === 'IMAP' || input.accountType === 'CALDAV';
|
||||
|
||||
shouldEnableSync = Boolean(hadOnlySmtp && isAddingImapOrCaldav);
|
||||
}
|
||||
|
||||
await workspaceDataSource.transaction(async () => {
|
||||
if (!existingAccountId) {
|
||||
const newConnectedAccount = await connectedAccountRepository.save(
|
||||
@ -129,7 +143,10 @@ export class ImapSmtpCalDavAPIService {
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
type: MessageChannelType.EMAIL,
|
||||
handle,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
isSyncEnabled: shouldEnableSync,
|
||||
syncStatus: shouldEnableSync
|
||||
? MessageChannelSyncStatus.ONGOING
|
||||
: MessageChannelSyncStatus.NOT_SYNCED,
|
||||
},
|
||||
{},
|
||||
);
|
||||
@ -200,9 +217,12 @@ export class ImapSmtpCalDavAPIService {
|
||||
},
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
syncStatus: null,
|
||||
syncStatus: shouldEnableSync
|
||||
? MessageChannelSyncStatus.ONGOING
|
||||
: MessageChannelSyncStatus.NOT_SYNCED,
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
isSyncEnabled: shouldEnableSync,
|
||||
},
|
||||
);
|
||||
|
||||
@ -227,22 +247,24 @@ export class ImapSmtpCalDavAPIService {
|
||||
}
|
||||
});
|
||||
|
||||
if (this.twentyConfigService.get('MESSAGING_PROVIDER_IMAP_ENABLED')) {
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: {
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
},
|
||||
});
|
||||
if (!shouldEnableSync) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.messageQueueService.add<MessagingMessageListFetchJobData>(
|
||||
MessagingMessageListFetchJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
messageChannelId: messageChannel.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: {
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
},
|
||||
});
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.messageQueueService.add<MessagingMessageListFetchJobData>(
|
||||
MessagingMessageListFetchJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
messageChannelId: messageChannel.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,14 +64,14 @@ export class ImapClientProvider {
|
||||
await client.connect();
|
||||
|
||||
this.logger.log(
|
||||
`Connected to IMAP server for ${connectionParameters.handle}`,
|
||||
`Connected to IMAP server for ${connectedAccount.handle}`,
|
||||
);
|
||||
|
||||
try {
|
||||
const mailboxes = await client.list();
|
||||
|
||||
this.logger.log(
|
||||
`Available mailboxes for ${connectionParameters.handle}: ${mailboxes.map((m) => m.path).join(', ')}`,
|
||||
`Available mailboxes for ${connectedAccount.handle}: ${mailboxes.map((m) => m.path).join(', ')}`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.warn(`Failed to list mailboxes: ${error.message}`);
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { SmtpClientProvider } from './providers/smtp-client.provider';
|
||||
|
||||
@Module({
|
||||
providers: [SmtpClientProvider],
|
||||
exports: [SmtpClientProvider],
|
||||
})
|
||||
export class MessagingSmtpDriverModule {}
|
||||
@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { createTransport, Transporter } from 'nodemailer';
|
||||
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class SmtpClientProvider {
|
||||
public async getSmtpClient(
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
'connectionParameters' | 'handle'
|
||||
>,
|
||||
): Promise<Transporter> {
|
||||
const smtpParams = connectedAccount.connectionParameters?.SMTP;
|
||||
|
||||
if (!smtpParams) {
|
||||
throw new Error('SMTP settings not configured for this account');
|
||||
}
|
||||
|
||||
const transporter = createTransport({
|
||||
host: smtpParams.host,
|
||||
port: smtpParams.port,
|
||||
auth: {
|
||||
user: connectedAccount.handle,
|
||||
pass: smtpParams.password,
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
|
||||
return transporter;
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,7 @@ import { MessagingOngoingStaleCronJob } from 'src/modules/messaging/message-impo
|
||||
import { MessagingGmailDriverModule } from 'src/modules/messaging/message-import-manager/drivers/gmail/messaging-gmail-driver.module';
|
||||
import { MessagingIMAPDriverModule } from 'src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module';
|
||||
import { MessagingMicrosoftDriverModule } from 'src/modules/messaging/message-import-manager/drivers/microsoft/messaging-microsoft-driver.module';
|
||||
import { MessagingSmtpDriverModule } from 'src/modules/messaging/message-import-manager/drivers/smtp/messaging-smtp-driver.module';
|
||||
import { MessagingAddSingleMessageToCacheForImportJob } from 'src/modules/messaging/message-import-manager/jobs/messaging-add-single-message-to-cache-for-import.job';
|
||||
import { MessagingCleanCacheJob } from 'src/modules/messaging/message-import-manager/jobs/messaging-clean-cache';
|
||||
import { MessagingMessageListFetchJob } from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job';
|
||||
@ -48,6 +49,7 @@ import { MessagingMonitoringModule } from 'src/modules/messaging/monitoring/mess
|
||||
MessagingGmailDriverModule,
|
||||
MessagingMicrosoftDriverModule,
|
||||
MessagingIMAPDriverModule,
|
||||
MessagingSmtpDriverModule,
|
||||
MessagingCommonModule,
|
||||
TypeOrmModule.forFeature(
|
||||
[Workspace, DataSourceEntity, ObjectMetadataEntity],
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
import { GmailClientProvider } from 'src/modules/messaging/message-import-manager/drivers/gmail/providers/gmail-client.provider';
|
||||
import { OAuth2ClientProvider } from 'src/modules/messaging/message-import-manager/drivers/gmail/providers/oauth2-client.provider';
|
||||
import { MicrosoftClientProvider } from 'src/modules/messaging/message-import-manager/drivers/microsoft/providers/microsoft-client.provider';
|
||||
import { SmtpClientProvider } from 'src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider';
|
||||
import { isAccessTokenRefreshingError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-access-token-refreshing-error.utils';
|
||||
import { mimeEncode } from 'src/modules/messaging/message-import-manager/utils/mime-encode.util';
|
||||
|
||||
@ -27,6 +28,7 @@ export class MessagingSendMessageService {
|
||||
private readonly gmailClientProvider: GmailClientProvider,
|
||||
private readonly oAuth2ClientProvider: OAuth2ClientProvider,
|
||||
private readonly microsoftClientProvider: MicrosoftClientProvider,
|
||||
private readonly smtpClientProvider: SmtpClientProvider,
|
||||
) {}
|
||||
|
||||
public async sendMessage(
|
||||
@ -120,7 +122,16 @@ export class MessagingSendMessageService {
|
||||
break;
|
||||
}
|
||||
case ConnectedAccountProvider.IMAP_SMTP_CALDAV: {
|
||||
throw new Error('IMAP provider does not support sending messages');
|
||||
const smtpClient =
|
||||
await this.smtpClientProvider.getSmtpClient(connectedAccount);
|
||||
|
||||
await smtpClient.sendMail({
|
||||
from: connectedAccount.handle,
|
||||
to: sendMessageInput.to,
|
||||
subject: sendMessageInput.subject,
|
||||
text: sendMessageInput.body,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assertUnreachable(
|
||||
|
||||
Reference in New Issue
Block a user