Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: disable tracking in transactional email #2241

Merged
merged 6 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ RUN apk update && apk upgrade && apk add --no-cache --virtual builds-deps build-

RUN apk add jq

RUN python3 -m pip install awscli
# There was a breaking change in the base image used that prevents us from installing via pip
# Instead of activating a virtual env, this is a simpler workaround
# https://github.com/python/cpython/issues/102134
RUN apk add --no-cache aws-cli

RUN aws configure set default.region ap-southeast-1

Expand Down
6 changes: 6 additions & 0 deletions backend/src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ interface ConfigSchema {
}
mailFrom: string
mailConfigurationSet: string
noTrackingMailConfigurationSet: string
mailVia: string
mailDefaultRate: number
transactionalEmail: {
Expand Down Expand Up @@ -468,6 +469,11 @@ const config: Config<ConfigSchema> = convict({
default: 'postman-email-open',
env: 'BACKEND_SES_CONFIGURATION_SET',
},
noTrackingMailConfigurationSet: {
KishenKumarrrrr marked this conversation as resolved.
Show resolved Hide resolved
doc: 'AWS SES Configuration set that does not include open and read tracking',
default: 'postman-email-no-tracking',
env: 'BACKEND_SES_NO_TRACKING_CONFIGURATION_SET',
},
mailVia: {
doc: 'Text to appended to custom sender name',
default: 'via Postman',
Expand Down
3 changes: 2 additions & 1 deletion backend/src/core/services/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ const mailClient = new MailClient(
config.get('mailOptions'),
config.get('emailCallback.hashSecret'),
config.get('emailFallback.activate') ? config.get('mailFrom') : undefined,
config.get('mailConfigurationSet')
config.get('mailConfigurationSet'),
config.get('noTrackingMailConfigurationSet')
)

export const MailService = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const InitEmailTransactionalMiddleware = (
tag?: string
cc?: string[]
bcc?: string[]
disable_tracking?: boolean
}
type ReqBodyWithId = ReqBody & { emailMessageTransactionalId: string }

Expand Down Expand Up @@ -210,6 +211,7 @@ export const InitEmailTransactionalMiddleware = (
cc,
bcc,
emailMessageTransactionalId, // added by saveMessage middleware
disable_tracking: disableTracking,
} = req.body

try {
Expand Down Expand Up @@ -275,6 +277,7 @@ export const InitEmailTransactionalMiddleware = (
? bcc.filter((c) => !blacklistedRecipients.includes(c))
: undefined,
emailMessageTransactionalId,
disableTracking,
})
emailMessageTransactional.set(
'status',
Expand Down
1 change: 1 addition & 0 deletions backend/src/email/routes/email-transactional.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const InitEmailTransactionalRoute = (
.items(
Joi.string().trim().email().options({ convert: true }).lowercase()
),
disable_tracking: Joi.boolean().default(false),
}),
}
const getByIdValidator = {
Expand Down
3 changes: 3 additions & 0 deletions backend/src/email/services/email-transactional.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ async function sendMessage({
cc,
bcc,
emailMessageTransactionalId,
disableTracking,
}: {
subject: string
body: string
Expand All @@ -51,6 +52,7 @@ async function sendMessage({
cc?: string[]
bcc?: string[]
emailMessageTransactionalId: string
disableTracking?: boolean
}): Promise<void> {
// TODO: flagging this coupling for future refactoring:
// currently, we are using EmailTemplateService to sanitize both tx emails and campaign emails
Expand Down Expand Up @@ -99,6 +101,7 @@ async function sendMessage({
// receive from SES, but not saving to DB
const isEmailSent = await EmailService.sendEmail(mailToSend, {
extraSmtpHeaders: { isTransactional: true },
disableTracking,
})
if (!isEmailSent) {
throw new Error('Failed to send transactional email')
Expand Down
43 changes: 32 additions & 11 deletions shared/src/clients/mail-client.class/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@ export * from './interfaces'

export type SendEmailOpts = {
extraSmtpHeaders: Record<string, any>
disableTracking?: boolean
}

export default class MailClient {
private mailer: nodemailer.Transporter
private email: string
private hashSecret: string
private configSet: string | undefined
private defaultConfigSet: string | undefined
private noTrackingConfigSet: string | undefined

constructor(
credentials: MailCredentials,
hashSecret: string,
email?: string,
configSet?: string
defaultConfigSet?: string,
noTrackingConfigSet?: string
) {
const { host, port, auth } = credentials
this.hashSecret = hashSecret
Expand All @@ -35,7 +38,8 @@ export default class MailClient {
pass: auth.pass,
},
})
this.configSet = configSet
this.defaultConfigSet = defaultConfigSet
this.noTrackingConfigSet = noTrackingConfigSet
}

public sendMail(
Expand All @@ -61,14 +65,7 @@ export default class MailClient {
let headers: any = {
[REFERENCE_ID_HEADER]: JSON.stringify(xSmtpHeader),
}
if (this.configSet) {
headers = {
...headers,
// Specify this to configure callback endpoint for notifications other
// than delivery and bounce through SES configuration set
[CONFIGURATION_SET_HEADER]: this.configSet,
}
}
headers = this.setSesConfigurationHeader(headers, option?.disableTracking)
if (input.unsubLink) {
headers = {
...headers,
Expand Down Expand Up @@ -96,4 +93,28 @@ export default class MailClient {
})
})
}

private setSesConfigurationHeader(
headers: object,
disableTracking: boolean | undefined
): object {
// 1. If there is no default config set, we will not set any configuration header
if (!this.defaultConfigSet) {
return headers
}
// 2. If the user wants to disable tracking and there is a no tracking configuration, we set it
if (disableTracking && this.noTrackingConfigSet) {
return {
...headers,
// Configuration header does not include open and read notification
[CONFIGURATION_SET_HEADER]: this.noTrackingConfigSet,
}
}
// 3. Otherwise, we will use the default tracking SES configuration set
return {
...headers,
// Configuration header includes open and read notification
[CONFIGURATION_SET_HEADER]: this.defaultConfigSet,
}
}
}
5 changes: 4 additions & 1 deletion worker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ RUN apk update && apk upgrade && apk add --no-cache --virtual builds-deps build-

RUN apk add jq

RUN python3 -m pip install awscli
# There was a breaking change in the base image used that prevents us from installing via pip
# Instead of activating a virtual env, this is a simpler workaround
# https://github.com/python/cpython/issues/102134
RUN apk add --no-cache aws-cli
KishenKumarrrrr marked this conversation as resolved.
Show resolved Hide resolved

RUN aws configure set default.region ap-southeast-1

Expand Down
Loading