import {SendEmailCommand, SESClient} from "@aws-sdk/client-ses"; import {Validator} from "jsonschema"; import {QueryResult} from "pg"; import {log_error, isValidateEmail} from "./utils"; import emailRequestSchema from "../json_schemas/email-request-schema"; import db from "../config/db"; const sesClient = new SESClient({region: process.env.AWS_REGION}); export interface IEmail { to?: string[]; subject: string; html: string; } export class EmailRequest implements IEmail { public readonly html: string; public readonly subject: string; public readonly to: string[]; constructor(toEmails: string[], subject: string, content: string) { this.to = toEmails; this.subject = subject; this.html = content; } } function isValidMailBody(body: IEmail) { const validator = new Validator(); return validator.validate(body, emailRequestSchema).valid; } async function removeMails(query: string, emails: string[]) { const result: QueryResult<{ email: string; }> = await db.query(query, []); const bouncedEmails = result.rows.map(e => e.email); for (let i = emails.length - 1; i >= 0; i--) { const email = emails[i]; if (bouncedEmails.includes(email)) { emails.splice(i, 1); } } } async function filterSpamEmails(emails: string[]): Promise { await removeMails("SELECT email FROM spam_emails ORDER BY email;", emails); } async function filterBouncedEmails(emails: string[]): Promise { await removeMails("SELECT email FROM bounced_emails ORDER BY email;", emails); } export async function sendEmail(email: IEmail): Promise { try { const options = {...email} as IEmail; options.to = Array.isArray(options.to) ? Array.from(new Set(options.to)) : []; // Filter out empty, null, undefined, and invalid emails options.to = options.to .filter(email => email && typeof email === 'string' && email.trim().length > 0) .map(email => email.trim()) .filter(email => isValidateEmail(email)); if (options.to.length) { await filterBouncedEmails(options.to); await filterSpamEmails(options.to); } // Double-check that we still have valid emails after filtering if (!options.to.length) return null; if (!isValidMailBody(options)) return null; const charset = "UTF-8"; const command = new SendEmailCommand({ Destination: { ToAddresses: options.to }, Message: { Subject: { Charset: charset, Data: options.subject }, Body: { Html: { Charset: charset, Data: options.html } } }, Source: "Worklenz " }); const res = await sesClient.send(command); return res.MessageId || null; } catch (e) { log_error(e); } return null; }