Skip to content

Commit

Permalink
Merge pull request #4042 from activepieces/chore/refactor-email-sending
Browse files Browse the repository at this point in the history
refactor: send emails in production only
  • Loading branch information
khaledmashaly committed Feb 27, 2024
2 parents 2e12d89 + e0b1b9e commit ce14557
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 173 deletions.
@@ -0,0 +1,56 @@
import { ApEnvironment } from '@activepieces/shared'
import { system, SystemProp } from 'server-shared'
import { logEmailSender } from './log-email-sender'
import { smtpEmailSender } from './smtp-email-sender'

export type EmailSender = {
send: (args: SendArgs) => Promise<void>
}

const getEmailSenderInstance = (): EmailSender => {
const env = system.get(SystemProp.ENVIRONMENT)

if (env === ApEnvironment.PRODUCTION) {
return smtpEmailSender
}

return logEmailSender
}

export const emailSender = getEmailSenderInstance()

type BaseEmailTemplateData<Name extends string, Vars extends Record<string, string>> = {
name: Name
vars: Vars
}

type InvitationEmailTemplateData = BaseEmailTemplateData<'invitation-email', {
projectName: string
setupLink: string
}>

type QuotaEmailTemplateData = BaseEmailTemplateData<'quota-50' | 'quota-90' | 'quota-100', {
resetDate: string
firstName: string
}>

type ResetPasswordEmailTemplateData = BaseEmailTemplateData<'reset-password', {
setupLink: string
firstName: string
}>

type VerifyEmailTemplateData = BaseEmailTemplateData<'verify-email', {
setupLink: string
}>

export type EmailTemplateData =
| InvitationEmailTemplateData
| QuotaEmailTemplateData
| ResetPasswordEmailTemplateData
| VerifyEmailTemplateData

type SendArgs = {
email: string
platformId: string | undefined
templateData: EmailTemplateData
}
@@ -0,0 +1,16 @@
import { logger } from 'server-shared'
import { EmailSender } from './email-sender'

/**
* Logs sent emails to the console
*/
export const logEmailSender: EmailSender = {
async send({ email, platformId, templateData }) {
logger.debug({
name: 'LogEmailSender#send',
email,
platformId,
templateData,
})
},
}
@@ -0,0 +1,84 @@
import { readFile } from 'node:fs/promises'
import Mustache from 'mustache'
import nodemailer, { Transporter } from 'nodemailer'
import { Platform } from '@activepieces/ee-shared'
import { SystemProp, system } from 'server-shared'
import { platformService } from '../../../platform/platform.service'
import { EmailSender, EmailTemplateData } from './email-sender'
import { defaultTheme } from '../../../../flags/theme'

/**
* Sends emails using SMTP
*/
export const smtpEmailSender: EmailSender = {
async send({ email, platformId, templateData }) {
const platform = await getPlatform(platformId)
const emailSubject = getEmailSubject(templateData.name)
const senderName = platform?.name ?? system.get(SystemProp.SMTP_SENDER_NAME)
const senderEmail = platform?.smtpSenderEmail ?? system.get(SystemProp.SMTP_SENDER_EMAIL)

const emailBody = await renderEmailBody({
platform,
templateData,
})

const smtpClient = initSmtpClient(platform)

await smtpClient.sendMail({
from: `${senderName} <${senderEmail}>`,
to: email,
subject: emailSubject,
html: emailBody,
})
},
}

const getPlatform = async (platformId: string | undefined): Promise<Platform | null> => {
return platformId ? platformService.getOne(platformId) : null
}

const renderEmailBody = async ({ platform, templateData }: RenderEmailBodyArgs): Promise<string> => {
const templatePath = `packages/server/api/src/assets/emails/${templateData.name}.html`
const template = await readFile(templatePath, 'utf-8')

const primaryColor = platform?.primaryColor ?? defaultTheme.colors.primary.default
const fullLogoUrl = platform?.fullLogoUrl ?? defaultTheme.logos.fullLogoUrl
const platformName = platform?.name ?? defaultTheme.websiteName

return Mustache.render(template, {
...templateData.vars,
primaryColor,
fullLogoUrl,
platformName,
})
}

const initSmtpClient = (platform: Platform | null): Transporter => {
return nodemailer.createTransport({
host: platform?.smtpHost ?? system.getOrThrow(SystemProp.SMTP_HOST),
port: platform?.smtpPort ?? Number.parseInt(system.getOrThrow(SystemProp.SMTP_PORT)),
secure: platform?.smtpUseSSL ?? system.getBoolean(SystemProp.SMTP_USE_SSL),
auth: {
user: platform?.smtpUser ?? system.getOrThrow(SystemProp.SMTP_USERNAME),
pass: platform?.smtpPassword ?? system.getOrThrow(SystemProp.SMTP_PASSWORD),
},
})
}

const getEmailSubject = (templateName: EmailTemplateData['name']): string => {
const templateToSubject: Record<EmailTemplateData['name'], string> = {
'invitation-email': 'You have been invited to a team',
'quota-50': '[ACTION REQUIRED] 50% of your Activepieces tasks are consumed',
'quota-90': '[URGENT] 90% of your Activepieces tasks are consumed',
'quota-100': '[URGENT] 100% of your Activepieces tasks are consumed',
'verify-email': 'Verify your email address',
'reset-password': 'Reset your password',
}

return templateToSubject[templateName]
}

type RenderEmailBodyArgs = {
platform: Platform | null
templateData: EmailTemplateData
}

0 comments on commit ce14557

Please sign in to comment.