- Added isValidateEmail utility function to validate email addresses before sending. - Updated email filtering logic to remove empty, null, undefined, and invalid emails from the recipient list. - Reversed the iteration order in removeMails function to prevent index issues while splicing bounced emails. - Ensured that valid emails are present after filtering before proceeding with the email sending process.
102 lines
2.8 KiB
TypeScript
102 lines
2.8 KiB
TypeScript
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<void> {
|
|
await removeMails("SELECT email FROM spam_emails ORDER BY email;", emails);
|
|
}
|
|
|
|
async function filterBouncedEmails(emails: string[]): Promise<void> {
|
|
await removeMails("SELECT email FROM bounced_emails ORDER BY email;", emails);
|
|
}
|
|
|
|
export async function sendEmail(email: IEmail): Promise<string | null> {
|
|
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 <noreply@worklenz.com>"
|
|
});
|
|
|
|
const res = await sesClient.send(command);
|
|
return res.MessageId || null;
|
|
} catch (e) {
|
|
log_error(e);
|
|
}
|
|
|
|
return null;
|
|
}
|