Skip to content

Commit

Permalink
feat: ensure total recipients within SES limit (#2125)
Browse files Browse the repository at this point in the history
  • Loading branch information
zxt-tzx authored Jul 28, 2023
1 parent d81bd40 commit 36061d0
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 0 deletions.
5 changes: 5 additions & 0 deletions backend/src/core/errors/rest-api.errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export class ApiInvalidRecipientError extends RestApiError {
super(400, 'invalid_recipient', message)
}
}
export class ApiTooManyRecipientsError extends RestApiError {
constructor(message: string) {
super(400, 'too_many_recipients', message)
}
}

export class ApiInvalidCredentialsError extends RestApiError {
constructor(message: string) {
Expand Down
13 changes: 13 additions & 0 deletions backend/src/email/middlewares/email-transactional.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
ApiInvalidTemplateError,
ApiNotFoundError,
ApiRateLimitError,
ApiTooManyRecipientsError,
} from '@core/errors/rest-api.errors'
import { UploadedFile } from 'express-fileupload'
import { Op } from 'sequelize'
Expand All @@ -41,6 +42,7 @@ export interface EmailTransactionalMiddleware {
rateLimit: Handler
getById: Handler
listMessages: Handler
checkCcLimit: Handler
}

export const RATE_LIMIT_ERROR_MESSAGE =
Expand Down Expand Up @@ -365,6 +367,16 @@ export const InitEmailTransactionalMiddleware = (
})
}

function checkCcLimit(req: Request, _res: Response, next: NextFunction) {
const { cc, bcc } = req.body as ReqBody
// limit set by AWS SES: https://docs.aws.amazon.com/ses/latest/APIReference/API_SendEmail.html#:~:text=The%20message%20may%20not%20include%20more%20than%2050%20recipients
const TOTAL_CC_LIMIT = 49 // 50 minus 1 to account for main recipient
if ((cc?.length ?? 0) + (bcc?.length ?? 0) > TOTAL_CC_LIMIT) {
throw new ApiTooManyRecipientsError('Maximum of 50 recipients allowed')
}
next()
}

const rateLimit = expressRateLimit({
store: new RedisStore({
prefix: 'transactionalEmail:',
Expand All @@ -390,5 +402,6 @@ export const InitEmailTransactionalMiddleware = (
rateLimit,
getById,
listMessages,
checkCcLimit,
}
}
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 @@ -115,6 +115,7 @@ export const InitEmailTransactionalRoute = (
FileAttachmentMiddleware.preprocessPotentialIncomingFile,
celebrate(sendValidator),
FileAttachmentMiddleware.checkAttachmentValidity,
emailTransactionalMiddleware.checkCcLimit,
emailTransactionalMiddleware.saveMessage,
emailMiddleware.isFromAddressAccepted,
emailMiddleware.existsFromAddress, // future todo: put a cache to reduce db hits
Expand Down

0 comments on commit 36061d0

Please sign in to comment.