mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 23:45:21 +02:00
abstract email provider from email service
This commit is contained in:
parent
4b007713a3
commit
118b2d037e
12 changed files with 238 additions and 151 deletions
15
apps/server/src/app/lib/email.ts
Normal file
15
apps/server/src/app/lib/email.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { ServerClient as PostmarkServerClient } from 'postmark'
|
||||||
|
import env from '../../env'
|
||||||
|
|
||||||
|
export function initializeEmailClient() {
|
||||||
|
switch (process.env.NX_EMAIL_PROVIDER) {
|
||||||
|
case 'postmark':
|
||||||
|
if (env.NX_EMAIL_PROVIDER_API_TOKEN) {
|
||||||
|
return new PostmarkServerClient(env.NX_EMAIL_PROVIDER_API_TOKEN)
|
||||||
|
} else {
|
||||||
|
throw new Error('Missing Postmark API token')
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid email provider')
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ import type {
|
||||||
IInsightService,
|
IInsightService,
|
||||||
ISecurityPricingService,
|
ISecurityPricingService,
|
||||||
IPlanService,
|
IPlanService,
|
||||||
IEmailService,
|
|
||||||
} from '@maybe-finance/server/features'
|
} from '@maybe-finance/server/features'
|
||||||
import {
|
import {
|
||||||
CryptoService,
|
CryptoService,
|
||||||
|
@ -55,10 +54,10 @@ import prisma from './prisma'
|
||||||
import plaid, { getPlaidWebhookUrl } from './plaid'
|
import plaid, { getPlaidWebhookUrl } from './plaid'
|
||||||
import teller, { getTellerWebhookUrl } from './teller'
|
import teller, { getTellerWebhookUrl } from './teller'
|
||||||
import stripe from './stripe'
|
import stripe from './stripe'
|
||||||
import postmark from './postmark'
|
|
||||||
import defineAbilityFor from './ability'
|
import defineAbilityFor from './ability'
|
||||||
import env from '../../env'
|
import env from '../../env'
|
||||||
import logger from '../lib/logger'
|
import logger from '../lib/logger'
|
||||||
|
import { initializeEmailClient } from './email'
|
||||||
|
|
||||||
// shared services
|
// shared services
|
||||||
|
|
||||||
|
@ -73,12 +72,12 @@ export const queueService = new QueueService(
|
||||||
: new BullQueueFactory(logger.child({ service: 'BullQueueFactory' }), env.NX_REDIS_URL)
|
: new BullQueueFactory(logger.child({ service: 'BullQueueFactory' }), env.NX_REDIS_URL)
|
||||||
)
|
)
|
||||||
|
|
||||||
export const emailService: IEmailService = new EmailService(
|
export const emailService: EmailService = new EmailService(
|
||||||
logger.child({ service: 'EmailService' }),
|
logger.child({ service: 'EmailService' }),
|
||||||
postmark,
|
initializeEmailClient(),
|
||||||
{
|
{
|
||||||
from: env.NX_POSTMARK_FROM_ADDRESS,
|
from: env.NX_EMAIL_FROM_ADDRESS,
|
||||||
replyTo: env.NX_POSTMARK_REPLY_TO_ADDRESS,
|
replyTo: env.NX_EMAIL_REPLY_TO_ADDRESS,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { ServerClient } from 'postmark'
|
|
||||||
import env from '../../env'
|
|
||||||
|
|
||||||
const postmark = env.NX_POSTMARK_API_TOKEN ? new ServerClient(env.NX_POSTMARK_API_TOKEN) : undefined
|
|
||||||
|
|
||||||
export default postmark
|
|
|
@ -66,9 +66,9 @@ const envSchema = z.object({
|
||||||
// Key to Cloudfront pub key
|
// Key to Cloudfront pub key
|
||||||
NX_CDN_SIGNER_PUBKEY_ID: z.string().default('REPLACE_THIS'),
|
NX_CDN_SIGNER_PUBKEY_ID: z.string().default('REPLACE_THIS'),
|
||||||
|
|
||||||
NX_POSTMARK_FROM_ADDRESS: z.string().default('account@maybe.co'),
|
NX_EMAIL_FROM_ADDRESS: z.string().default('account@maybe.co'),
|
||||||
NX_POSTMARK_REPLY_TO_ADDRESS: z.string().default('support@maybe.co'),
|
NX_EMAIL_REPLY_TO_ADDRESS: z.string().default('support@maybe.co'),
|
||||||
NX_POSTMARK_API_TOKEN: z.string().optional(),
|
NX_EMAIL_PROVIDER_API_TOKEN: z.string().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const env = envSchema.parse(process.env)
|
const env = envSchema.parse(process.env)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import type {
|
||||||
IUserProcessor,
|
IUserProcessor,
|
||||||
ISecurityPricingService,
|
ISecurityPricingService,
|
||||||
IUserService,
|
IUserService,
|
||||||
IEmailService,
|
|
||||||
IEmailProcessor,
|
IEmailProcessor,
|
||||||
} from '@maybe-finance/server/features'
|
} from '@maybe-finance/server/features'
|
||||||
import {
|
import {
|
||||||
|
@ -55,7 +54,7 @@ import logger from './logger'
|
||||||
import prisma from './prisma'
|
import prisma from './prisma'
|
||||||
import plaid from './plaid'
|
import plaid from './plaid'
|
||||||
import teller from './teller'
|
import teller from './teller'
|
||||||
import postmark from './postmark'
|
import { initializeEmailClient } from './email'
|
||||||
import stripe from './stripe'
|
import stripe from './stripe'
|
||||||
import env from '../../env'
|
import env from '../../env'
|
||||||
import { BullQueueEventHandler, WorkerErrorHandlerService } from '../services'
|
import { BullQueueEventHandler, WorkerErrorHandlerService } from '../services'
|
||||||
|
@ -263,12 +262,12 @@ export const workerErrorHandlerService = new WorkerErrorHandlerService(
|
||||||
|
|
||||||
// send-email
|
// send-email
|
||||||
|
|
||||||
export const emailService: IEmailService = new EmailService(
|
export const emailService: EmailService = new EmailService(
|
||||||
logger.child({ service: 'EmailService' }),
|
logger.child({ service: 'EmailService' }),
|
||||||
postmark,
|
initializeEmailClient(),
|
||||||
{
|
{
|
||||||
from: env.NX_POSTMARK_FROM_ADDRESS,
|
from: env.NX_EMAIL_FROM_ADDRESS,
|
||||||
replyTo: env.NX_POSTMARK_REPLY_TO_ADDRESS,
|
replyTo: env.NX_EMAIL_REPLY_TO_ADDRESS,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
15
apps/workers/src/app/lib/email.ts
Normal file
15
apps/workers/src/app/lib/email.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { ServerClient as PostmarkServerClient } from 'postmark'
|
||||||
|
import env from '../../env'
|
||||||
|
|
||||||
|
export function initializeEmailClient() {
|
||||||
|
switch (process.env.NX_EMAIL_PROVIDER) {
|
||||||
|
case 'postmark':
|
||||||
|
if (env.NX_EMAIL_PROVIDER_API_TOKEN) {
|
||||||
|
return new PostmarkServerClient(env.NX_EMAIL_PROVIDER_API_TOKEN)
|
||||||
|
} else {
|
||||||
|
throw new Error('Missing Postmark API token')
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid email provider')
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
import { ServerClient } from 'postmark'
|
|
||||||
import env from '../../env'
|
|
||||||
|
|
||||||
const postmark = env.NX_POSTMARK_API_TOKEN ? new ServerClient(env.NX_POSTMARK_API_TOKEN) : undefined
|
|
||||||
|
|
||||||
export default postmark
|
|
|
@ -21,9 +21,9 @@ const envSchema = z.object({
|
||||||
|
|
||||||
NX_POLYGON_API_KEY: z.string().default(''),
|
NX_POLYGON_API_KEY: z.string().default(''),
|
||||||
|
|
||||||
NX_POSTMARK_FROM_ADDRESS: z.string().default('account@maybe.co'),
|
NX_EMAIL_FROM_ADDRESS: z.string().default('account@maybe.co'),
|
||||||
NX_POSTMARK_REPLY_TO_ADDRESS: z.string().default('support@maybe.co'),
|
NX_EMAIL_REPLY_TO_ADDRESS: z.string().default('support@maybe.co'),
|
||||||
NX_POSTMARK_API_TOKEN: z.string().optional(),
|
NX_EMAIL_PROVIDER_API_TOKEN: z.string().optional(),
|
||||||
NX_STRIPE_SECRET_KEY: z.string().default('sk_test_REPLACE_THIS'),
|
NX_STRIPE_SECRET_KEY: z.string().default('sk_test_REPLACE_THIS'),
|
||||||
|
|
||||||
NX_CDN_PRIVATE_BUCKET: z.string().default('REPLACE_THIS'),
|
NX_CDN_PRIVATE_BUCKET: z.string().default('REPLACE_THIS'),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Logger } from 'winston'
|
import type { Logger } from 'winston'
|
||||||
import type { PrismaClient } from '@prisma/client'
|
import type { PrismaClient } from '@prisma/client'
|
||||||
import type { SendEmailQueueJobData } from '@maybe-finance/server/shared'
|
import type { SendEmailQueueJobData } from '@maybe-finance/server/shared'
|
||||||
import type { IEmailService } from './email.service'
|
import type { EmailService } from './email.service'
|
||||||
import { DateTime } from 'luxon'
|
import { DateTime } from 'luxon'
|
||||||
|
|
||||||
export interface IEmailProcessor {
|
export interface IEmailProcessor {
|
||||||
|
@ -13,17 +13,25 @@ export class EmailProcessor implements IEmailProcessor {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly prisma: PrismaClient,
|
private readonly prisma: PrismaClient,
|
||||||
private readonly emailService: IEmailService
|
private readonly emailService: EmailService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async send(jobData: SendEmailQueueJobData) {
|
async send(jobData: SendEmailQueueJobData) {
|
||||||
if ('type' in jobData) {
|
if ('type' in jobData) {
|
||||||
switch (jobData.type) {
|
switch (jobData.type) {
|
||||||
case 'plain':
|
case 'plain':
|
||||||
this.emailService.send(jobData.messages)
|
if (Array.isArray(jobData.messages)) {
|
||||||
|
this.emailService.send(jobData.messages)
|
||||||
|
} else {
|
||||||
|
this.emailService.send([jobData.messages])
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case 'template':
|
case 'template':
|
||||||
this.emailService.sendTemplate(jobData.messages)
|
if (Array.isArray(jobData.messages)) {
|
||||||
|
this.emailService.sendTemplate(jobData.messages)
|
||||||
|
} else {
|
||||||
|
this.emailService.sendTemplate([jobData.messages])
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case 'trial-reminders':
|
case 'trial-reminders':
|
||||||
this.sendTrialEndReminders()
|
this.sendTrialEndReminders()
|
||||||
|
|
|
@ -1,53 +1,60 @@
|
||||||
import type { Logger } from 'winston'
|
import type { Logger } from 'winston'
|
||||||
import type { Message, ServerClient as PostmarkServerClient, TemplatedMessage } from 'postmark'
|
import type { ServerClient as PostmarkServerClient } from 'postmark'
|
||||||
import type { MessageSendingResponse } from 'postmark/dist/client/models'
|
import type { MessageSendingResponse } from 'postmark/dist/client/models'
|
||||||
import type { SharedType } from '@maybe-finance/shared'
|
import type { SharedType } from '@maybe-finance/shared'
|
||||||
import { chunk, uniq } from 'lodash'
|
|
||||||
import { EmailTemplateSchema } from './email.schema'
|
|
||||||
|
|
||||||
export interface IEmailService {
|
import { PostmarkEmailProvider } from './providers/postmark.provider'
|
||||||
send(messages: SharedType.PlainEmailMessage): Promise<MessageSendingResponse>
|
|
||||||
send(messages: SharedType.PlainEmailMessage[]): Promise<MessageSendingResponse[]>
|
export interface IEmailProvider {
|
||||||
|
send(messages: SharedType.PlainEmailMessage): Promise<SharedType.EmailSendingResponse>
|
||||||
|
send(messages: SharedType.PlainEmailMessage[]): Promise<SharedType.EmailSendingResponse[]>
|
||||||
send(
|
send(
|
||||||
messages: SharedType.PlainEmailMessage | SharedType.PlainEmailMessage[]
|
messages: SharedType.PlainEmailMessage | SharedType.PlainEmailMessage[]
|
||||||
): Promise<MessageSendingResponse | MessageSendingResponse[]>
|
): Promise<any | any[]>
|
||||||
|
sendTemplate(
|
||||||
sendTemplate(messages: SharedType.TemplateEmailMessage): Promise<MessageSendingResponse>
|
messages: SharedType.TemplateEmailMessage
|
||||||
sendTemplate(messages: SharedType.TemplateEmailMessage[]): Promise<MessageSendingResponse[]>
|
): Promise<SharedType.EmailSendingResponse>
|
||||||
|
sendTemplate(
|
||||||
|
messages: SharedType.TemplateEmailMessage[]
|
||||||
|
): Promise<SharedType.EmailSendingResponse[]>
|
||||||
sendTemplate(
|
sendTemplate(
|
||||||
messages: SharedType.TemplateEmailMessage | SharedType.TemplateEmailMessage[]
|
messages: SharedType.TemplateEmailMessage | SharedType.TemplateEmailMessage[]
|
||||||
): Promise<MessageSendingResponse | MessageSendingResponse[]>
|
): Promise<SharedType.EmailSendingResponse | SharedType.EmailSendingResponse[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EmailService implements IEmailService {
|
export class EmailService implements IEmailProvider {
|
||||||
|
private emailProvider: IEmailProvider
|
||||||
constructor(
|
constructor(
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
private readonly postmark: PostmarkServerClient | undefined,
|
private readonly client: PostmarkServerClient | undefined,
|
||||||
private readonly defaultAddresses: { from: string; replyTo?: string }
|
private readonly defaultAddresses: { from: string; replyTo?: string }
|
||||||
) {}
|
) {
|
||||||
|
const provider = process.env.EMAIL_PROVIDER
|
||||||
|
|
||||||
|
switch (provider) {
|
||||||
|
case 'postmark':
|
||||||
|
this.emailProvider = new PostmarkEmailProvider(
|
||||||
|
this.logger.child({ service: 'PostmarkEmailProvider' }),
|
||||||
|
this.client,
|
||||||
|
this.defaultAddresses
|
||||||
|
)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error('Unsupported email provider')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends plain email(s)
|
* Sends plain email(s)
|
||||||
*
|
*
|
||||||
* @returns success boolean(s)
|
* @returns success boolean(s)
|
||||||
*/
|
*/
|
||||||
async send(messages: SharedType.PlainEmailMessage): Promise<MessageSendingResponse>
|
send(messages: SharedType.PlainEmailMessage): Promise<any>
|
||||||
async send(messages: SharedType.PlainEmailMessage[]): Promise<MessageSendingResponse[]>
|
send(messages: SharedType.PlainEmailMessage[]): Promise<any[]>
|
||||||
async send(
|
send(
|
||||||
messages: SharedType.PlainEmailMessage | SharedType.PlainEmailMessage[]
|
messages: SharedType.PlainEmailMessage | SharedType.PlainEmailMessage[]
|
||||||
): Promise<MessageSendingResponse | MessageSendingResponse[]> {
|
): Promise<any | any[]> {
|
||||||
const mapToPostmark = (message: SharedType.PlainEmailMessage): Message => ({
|
return this.emailProvider.send(messages)
|
||||||
From: message.from ?? this.defaultAddresses.from,
|
|
||||||
ReplyTo: message.replyTo ?? this.defaultAddresses.replyTo,
|
|
||||||
To: message.to,
|
|
||||||
Subject: message.subject,
|
|
||||||
TextBody: message.textBody,
|
|
||||||
HtmlBody: message.htmlBody,
|
|
||||||
})
|
|
||||||
|
|
||||||
return Array.isArray(messages)
|
|
||||||
? this.sendEmailBatch(messages.map(mapToPostmark))
|
|
||||||
: this.sendEmail(mapToPostmark(messages))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,92 +67,6 @@ export class EmailService implements IEmailService {
|
||||||
async sendTemplate(
|
async sendTemplate(
|
||||||
messages: SharedType.TemplateEmailMessage | SharedType.TemplateEmailMessage[]
|
messages: SharedType.TemplateEmailMessage | SharedType.TemplateEmailMessage[]
|
||||||
): Promise<MessageSendingResponse | MessageSendingResponse[]> {
|
): Promise<MessageSendingResponse | MessageSendingResponse[]> {
|
||||||
const mapToPostmark = (message: SharedType.TemplateEmailMessage): TemplatedMessage => {
|
return this.emailProvider.sendTemplate(messages)
|
||||||
const { alias, model } = EmailTemplateSchema.parse(message.template)
|
|
||||||
|
|
||||||
return {
|
|
||||||
From: message.from ?? this.defaultAddresses.from,
|
|
||||||
ReplyTo: message.replyTo ?? this.defaultAddresses.replyTo,
|
|
||||||
To: message.to,
|
|
||||||
TemplateAlias: alias,
|
|
||||||
TemplateModel: model,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.isArray(messages)
|
|
||||||
? this.sendEmailBatchWithTemplate(messages.map(mapToPostmark))
|
|
||||||
: this.sendEmailWithTemplate(mapToPostmark(messages))
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendEmailWithTemplate(
|
|
||||||
message: TemplatedMessage
|
|
||||||
): Promise<MessageSendingResponse> {
|
|
||||||
this.logger.info(
|
|
||||||
`Sending templated email template=${message.TemplateAlias} from=${message.From} to=${message.To}`,
|
|
||||||
message.TemplateModel
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!this.postmark) {
|
|
||||||
this.logger.info('Postmark API key not provided, skipping email send')
|
|
||||||
return undefined as unknown as MessageSendingResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.postmark.sendEmailWithTemplate(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendEmail(message: Message): Promise<MessageSendingResponse> {
|
|
||||||
this.logger.info(
|
|
||||||
`Sending plain email subject=${message.Subject} from=${message.From} to=${message.To}`,
|
|
||||||
{ text: message.TextBody, html: message.HtmlBody }
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!this.postmark) {
|
|
||||||
this.logger.info('Postmark API key not provided, skipping email send')
|
|
||||||
return undefined as unknown as MessageSendingResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.postmark.sendEmail(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendEmailBatchWithTemplate(
|
|
||||||
messages: TemplatedMessage[]
|
|
||||||
): Promise<MessageSendingResponse[]> {
|
|
||||||
this.logger.info(
|
|
||||||
`Sending templated email batch templates=[${uniq(
|
|
||||||
messages.map(({ TemplateAlias }) => TemplateAlias)
|
|
||||||
).join(',')}] count=${messages.length}`
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
await Promise.all(
|
|
||||||
chunk(messages, 500).map((chunk) => {
|
|
||||||
if (!this.postmark) {
|
|
||||||
this.logger.info('Postmark API key not provided, skipping email send')
|
|
||||||
return [] as MessageSendingResponse[]
|
|
||||||
}
|
|
||||||
return this.postmark.sendEmailBatchWithTemplates(chunk)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
).flat()
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendEmailBatch(messages: Message[]): Promise<MessageSendingResponse[]> {
|
|
||||||
this.logger.info(
|
|
||||||
`Sending templated email batch subjects=[${uniq(
|
|
||||||
messages.map(({ Subject }) => Subject)
|
|
||||||
).join(',')}] count=${messages.length}`
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
await Promise.all(
|
|
||||||
chunk(messages, 500).map((chunk) => {
|
|
||||||
if (!this.postmark) {
|
|
||||||
this.logger.info('Postmark API key not provided, skipping email send')
|
|
||||||
return [] as MessageSendingResponse[]
|
|
||||||
}
|
|
||||||
return this.postmark.sendEmailBatch(chunk)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
).flat()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
138
libs/server/features/src/email/providers/postmark.provider.ts
Normal file
138
libs/server/features/src/email/providers/postmark.provider.ts
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
import type { IEmailProvider } from '../email.service'
|
||||||
|
import type { Logger } from 'winston'
|
||||||
|
import type { Message, ServerClient as PostmarkServerClient, TemplatedMessage } from 'postmark'
|
||||||
|
import type { MessageSendingResponse } from 'postmark/dist/client/models'
|
||||||
|
import type { SharedType } from '@maybe-finance/shared'
|
||||||
|
import { chunk, uniq } from 'lodash'
|
||||||
|
import { EmailTemplateSchema } from '../email.schema'
|
||||||
|
|
||||||
|
export class PostmarkEmailProvider implements IEmailProvider {
|
||||||
|
constructor(
|
||||||
|
private readonly logger: Logger,
|
||||||
|
private readonly client: PostmarkServerClient | undefined,
|
||||||
|
private readonly defaultAddresses: { from: string; replyTo?: string }
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends plain email(s)
|
||||||
|
*
|
||||||
|
* @returns success boolean(s)
|
||||||
|
*/
|
||||||
|
async send(messages: SharedType.PlainEmailMessage): Promise<MessageSendingResponse>
|
||||||
|
async send(messages: SharedType.PlainEmailMessage[]): Promise<MessageSendingResponse[]>
|
||||||
|
async send(
|
||||||
|
messages: SharedType.PlainEmailMessage | SharedType.PlainEmailMessage[]
|
||||||
|
): Promise<MessageSendingResponse | MessageSendingResponse[]> {
|
||||||
|
const mapToPostmark = (message: SharedType.PlainEmailMessage): Message => ({
|
||||||
|
From: message.from ?? this.defaultAddresses.from,
|
||||||
|
ReplyTo: message.replyTo ?? this.defaultAddresses.replyTo,
|
||||||
|
To: message.to,
|
||||||
|
Subject: message.subject,
|
||||||
|
TextBody: message.textBody,
|
||||||
|
HtmlBody: message.htmlBody,
|
||||||
|
})
|
||||||
|
|
||||||
|
return Array.isArray(messages)
|
||||||
|
? this.sendEmailBatch(messages.map(mapToPostmark))
|
||||||
|
: this.sendEmail(mapToPostmark(messages))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends template email(s)
|
||||||
|
*/
|
||||||
|
async sendTemplate(messages: SharedType.TemplateEmailMessage): Promise<MessageSendingResponse>
|
||||||
|
async sendTemplate(
|
||||||
|
messages: SharedType.TemplateEmailMessage[]
|
||||||
|
): Promise<MessageSendingResponse[]>
|
||||||
|
async sendTemplate(
|
||||||
|
messages: SharedType.TemplateEmailMessage | SharedType.TemplateEmailMessage[]
|
||||||
|
): Promise<MessageSendingResponse | MessageSendingResponse[]> {
|
||||||
|
const mapToPostmark = (message: SharedType.TemplateEmailMessage): TemplatedMessage => {
|
||||||
|
const { alias, model } = EmailTemplateSchema.parse(message.template)
|
||||||
|
|
||||||
|
return {
|
||||||
|
From: message.from ?? this.defaultAddresses.from,
|
||||||
|
ReplyTo: message.replyTo ?? this.defaultAddresses.replyTo,
|
||||||
|
To: message.to,
|
||||||
|
TemplateAlias: alias,
|
||||||
|
TemplateModel: model,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.isArray(messages)
|
||||||
|
? this.sendEmailBatchWithTemplate(messages.map(mapToPostmark))
|
||||||
|
: this.sendEmailWithTemplate(mapToPostmark(messages))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendEmailWithTemplate(
|
||||||
|
message: TemplatedMessage
|
||||||
|
): Promise<MessageSendingResponse> {
|
||||||
|
this.logger.info(
|
||||||
|
`Sending templated email template=${message.TemplateAlias} from=${message.From} to=${message.To}`,
|
||||||
|
message.TemplateModel
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!this.client) {
|
||||||
|
this.logger.info('Postmark API key not provided, skipping email send')
|
||||||
|
return undefined as unknown as MessageSendingResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.client.sendEmailWithTemplate(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendEmail(message: Message): Promise<MessageSendingResponse> {
|
||||||
|
this.logger.info(
|
||||||
|
`Sending plain email subject=${message.Subject} from=${message.From} to=${message.To}`,
|
||||||
|
{ text: message.TextBody, html: message.HtmlBody }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!this.client) {
|
||||||
|
this.logger.info('Postmark API key not provided, skipping email send')
|
||||||
|
return undefined as unknown as MessageSendingResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.client.sendEmail(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendEmailBatchWithTemplate(
|
||||||
|
messages: TemplatedMessage[]
|
||||||
|
): Promise<MessageSendingResponse[]> {
|
||||||
|
this.logger.info(
|
||||||
|
`Sending templated email batch templates=[${uniq(
|
||||||
|
messages.map(({ TemplateAlias }) => TemplateAlias)
|
||||||
|
).join(',')}] count=${messages.length}`
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
await Promise.all(
|
||||||
|
chunk(messages, 500).map((chunk) => {
|
||||||
|
if (!this.client) {
|
||||||
|
this.logger.info('Postmark API key not provided, skipping email send')
|
||||||
|
return [] as MessageSendingResponse[]
|
||||||
|
}
|
||||||
|
return this.client.sendEmailBatchWithTemplates(chunk)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).flat()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendEmailBatch(messages: Message[]): Promise<MessageSendingResponse[]> {
|
||||||
|
this.logger.info(
|
||||||
|
`Sending templated email batch subjects=[${uniq(
|
||||||
|
messages.map(({ Subject }) => Subject)
|
||||||
|
).join(',')}] count=${messages.length}`
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
await Promise.all(
|
||||||
|
chunk(messages, 500).map((chunk) => {
|
||||||
|
if (!this.client) {
|
||||||
|
this.logger.info('Postmark API key not provided, skipping email send')
|
||||||
|
return [] as MessageSendingResponse[]
|
||||||
|
}
|
||||||
|
return this.client.sendEmailBatch(chunk)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).flat()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { MessageSendingResponse } from 'postmark/dist/client/models'
|
||||||
|
|
||||||
type EmailCommon = {
|
type EmailCommon = {
|
||||||
from?: string
|
from?: string
|
||||||
to: string
|
to: string
|
||||||
|
@ -10,3 +12,5 @@ type PlainMessageContent = { subject: string; textBody?: string; htmlBody?: stri
|
||||||
|
|
||||||
export type PlainEmailMessage = EmailCommon & PlainMessageContent
|
export type PlainEmailMessage = EmailCommon & PlainMessageContent
|
||||||
export type TemplateEmailMessage = EmailCommon & { template: EmailTemplate }
|
export type TemplateEmailMessage = EmailCommon & { template: EmailTemplate }
|
||||||
|
|
||||||
|
export type EmailSendingResponse = MessageSendingResponse
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue