@email-service/email-service
Version:
email-service is a versatile npm package designed to simplify the integration and standardization of email communications across multiple Email Service Providers (ESPs).
205 lines (204 loc) • 11.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmailServiceSelector = void 0;
exports.getEmailService = getEmailService;
exports.getWebHook = getWebHook;
const normalizeEmailRecipients_js_1 = require("../utils/normalizeEmailRecipients.js");
const brevo_js_1 = require("./ESP/brevo.js");
const emailService_js_1 = require("./ESP/emailService.js");
//import { NodeMailerEmailService } from "./ESP/nodeMailer.js";
const postMark_js_1 = require("./ESP/postMark.js");
const resend_js_1 = require("./ESP/resend.js");
class EmailServiceSelector {
constructor(service, opts) {
switch (service.esp) {
case 'postmark':
this.emailService = new postMark_js_1.PostMarkEmailService(service, opts);
break;
case 'nodemailer':
//this.emailService = new NodeMailerEmailService(service, opts);
break;
case 'brevo':
this.emailService = new brevo_js_1.BrevoEmailService(service, opts);
break;
case 'emailserviceviewer':
this.emailService = new emailService_js_1.ViewerEmailService(service, opts);
break;
case 'emailserviceviewerlocal':
this.emailService = new emailService_js_1.ViewerEmailService(service, opts);
break;
case 'resend':
this.emailService = new resend_js_1.ResendEmailService(service, opts);
break;
default:
throw new Error('Invalid ESP');
break;
}
}
/**
* Envoi en lot — délègue à l'ESP sous-jacent (`ESP.sendBulk` de base).
* Hooks + stream + rate limit appliqués automatiquement.
*/
async sendBulk(payload) {
if (!this.emailService) {
throw new Error('[sendBulk] no ESP configured');
}
return this.emailService.sendBulk(payload);
}
async sendEmail(email) {
if (!email)
return ({ success: false, status: 400, error: { name: 'NO_EMAIL', message: 'No email provided' } });
const typeOfPayload = Array.isArray(email) ? 'array' : (typeof email === 'object') ? 'object' : 'unknown';
if (typeOfPayload === 'unknown') {
return ({ success: false, status: 400, error: { name: 'INVALID_EMAIL_PAYLOAD', message: 'Invalid email payload type', cause: { type: typeof email } } });
}
// Unify email to an array if it is a single object
const emails = (Array.isArray(email) ? email : (typeof email === 'object') ? [email] : []);
if (emails.length === 0)
return ({ success: false, status: 400, error: { name: 'NO_EMAIL', message: 'No email provided' } });
if (this.emailService) {
// Verification of the emails
for (const email of emails) {
/* Verification of the sender */
const from = this.emailService.checkFrom(email.from);
if (!from)
return ({ success: false, status: 400, error: { name: 'INVALID_SENDER', message: 'Invalid sender in the email' } });
if (!(0, normalizeEmailRecipients_js_1.isValidEmail)(from.email))
return ({ success: false, status: 400, error: { name: 'INVALID_SENDER', message: 'Invalid sender in the email', cause: { from } } });
email.from = from;
/* Verification of the recipients */
// Formatting recipient addresses
email.to = this.emailService.checkRecipients(email.to);
// Check that there is at least one recipient in the email
if (!email.to || !Array.isArray(email.to))
return ({ success: false, status: 400, error: { name: 'NO_RECIPIENT', message: 'No recipient in the email' } });
// Check that there is at least one sender in the email
if (email.to.length === 0)
return ({ success: false, status: 400, error: { name: 'NO_RECIPIENT', message: 'No recipient in the email' } });
// Email verification:
const invalidRecipients = (0, normalizeEmailRecipients_js_1.checkValidityOfEmails)(email.to);
if (invalidRecipients.length > 0)
return ({ success: false, status: 400, error: { name: 'INVALID_RECIPIENT', message: 'Invalid recipient in the email', cause: invalidRecipients } });
/* Verification of CC */
if (email.cc) {
email.cc = this.emailService.checkRecipients(email.cc);
const invalidRecipientsCC = (0, normalizeEmailRecipients_js_1.checkValidityOfEmails)(email.cc);
if (invalidRecipientsCC.length > 0)
return ({ success: false, status: 400, error: { name: 'INVALID_RECIPIENT', message: 'Invalid recipient in the email', cause: invalidRecipientsCC } });
}
/* Verification of BCC */
if (email.bcc) {
email.bcc = this.emailService.checkRecipients(email.bcc);
const invalidRecipientsBCC = (0, normalizeEmailRecipients_js_1.checkValidityOfEmails)(email.bcc);
if (invalidRecipientsBCC.length > 0)
return ({ success: false, status: 400, error: { name: 'INVALID_RECIPIENT', message: 'Invalid recipient in the email', cause: invalidRecipientsBCC } });
}
const recipients = email.to.concat(email.cc ? email.cc : []).concat(email.bcc ? email.bcc : []);
// No more than 50 recipients
if (recipients.length > 50)
return ({ success: false, status: 400, error: { name: 'TOO_MANY_RECIPIENTS', message: 'Too many recipients in the email (50 max)', cause: { number: recipients.length, emails: recipients } } });
// Check that there is a subject in the email
if (!email.subject)
return ({ success: false, status: 400, error: { name: 'NO_SUBJECT', message: 'No subject in the email' } });
// Check that there is content in the email
if (!email.html || !email.text)
return ({ success: false, status: 400, error: { name: 'NO_CONTENT', message: 'No content in the email' } });
}
// if one email
if (emails.length === 1) {
const resultat = await this.emailService.sendMail(emails[0]);
if (typeOfPayload === 'object') {
// If the payload is a single object, return the response directly
return resultat;
}
else {
// If the payload is an array, return the response in an array
return [resultat];
}
}
// Multiple emails
else if (this.emailService.mailMultiple && this.emailService.mailMultiple === true) {
// If the email service supports sending multiple emails at by 500 once
console.log('******** ES-Email ******** Sending multiple emails at once');
return await this.emailService.sendMailMultiple(emails);
}
else {
// If the email service does not support sending multiple emails at once, send them one by one
console.log('******** ES-Email ******** Sending multiple emails one by one');
const responses = [];
for (const email of emails) {
const response = await this.emailService.sendMail(email);
responses.push(response);
}
return responses;
}
}
else
return ({ success: false, status: 500, error: { name: 'NO_ESP', message: 'No ESP service configured' } });
}
static async sendEmail(esp, email) {
const emailServiceSelector = new EmailServiceSelector(esp);
// Un EmailPayload unique → toujours une StandardResponse unique (pas un tableau).
// Le typage union de l'instance couvre aussi le cas tableau ; on cast pour la signature statique.
return await emailServiceSelector.sendEmail(email);
}
close() {
// Nothing, as we are using only for nodemailer
}
static async webHook(esp, req, logger = false) {
if (esp) {
if (logger)
console.log("******** ES-WebHook ******** esp", esp);
// Prefix matching : les ESP envoient un User-Agent avec version
// (ex: "Postmark HTTPClient 1.2.3", "Svix-Webhooks/1.84.0 (sender-X...)",
// "SendinBlue Webhook/2.0"). Un match exact casse dès que le provider
// upgrade sa version, d'où le startsWith.
const config = { esp: 'emailserviceviewer', logger: logger };
if (esp.startsWith('Postmark')) {
config.esp = 'postmark';
}
else if (esp.startsWith('SendinBlue') || esp.startsWith('Brevo')) {
config.esp = 'brevo';
}
else if (esp.startsWith('Svix-Webhooks')) {
// Svix est le provider de webhook utilisé par Resend.
config.esp = 'resend';
}
else if (esp === 'email-service-viewer') {
config.esp = 'emailserviceviewer';
}
else if (esp === 'nodemailer') {
return ({ success: false, status: 500, error: { name: 'NO_NODEMAILER', message: 'No webhook traitement for nodemailer' } });
}
else {
return ({ success: false, status: 500, error: { name: 'INVALID_ESP', message: 'No ESP service configured for ' + esp } });
}
if (logger)
console.log("******** ES-WebHook ******** config", config);
// @ts-ignore
const emailESP = new EmailServiceSelector(config);
if (logger)
console.log("******** ES-WebHook ******** emailESP", emailESP);
if (emailESP.emailService) {
return await emailESP.emailService.webHookManagement(req);
}
else {
return ({ success: false, status: 500, error: { name: 'NO_ESP', message: 'No ESP service configured' } });
}
}
else {
return ({ success: false, status: 500, error: { name: 'NO_ESP', message: 'No ESP service configured' } });
}
}
}
exports.EmailServiceSelector = EmailServiceSelector;
function getEmailService(service, opts) {
return new EmailServiceSelector(service, opts);
}
async function getWebHook(userAgent, req, logger = false) {
if (logger) {
console.log('******** ES-WebHook ******** userAgent, logger', userAgent, logger);
console.log('******** ES-WebHook ******** req', req);
}
return await EmailServiceSelector.webHook(userAgent, req, logger);
}