From bf55ebc81fa8d3cc4aa4e6fd3735ff0ee659505a Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Sat, 21 Jan 2023 13:53:26 -0500 Subject: Add mailjet transport --- src/util/config/types/EmailConfiguration.ts | 2 + .../types/subconfigurations/email/MailJet.ts | 22 +++ .../config/types/subconfigurations/email/index.ts | 1 + src/util/util/Email.ts | 163 ++++++++++++++------- 4 files changed, 133 insertions(+), 55 deletions(-) create mode 100644 src/util/config/types/subconfigurations/email/MailJet.ts (limited to 'src') diff --git a/src/util/config/types/EmailConfiguration.ts b/src/util/config/types/EmailConfiguration.ts index 34550f4c..625507f2 100644 --- a/src/util/config/types/EmailConfiguration.ts +++ b/src/util/config/types/EmailConfiguration.ts @@ -18,6 +18,7 @@ import { MailGunConfiguration, + MailJetConfiguration, SMTPConfiguration, } from "./subconfigurations/email"; @@ -25,4 +26,5 @@ export class EmailConfiguration { provider: string | null = null; smtp: SMTPConfiguration = new SMTPConfiguration(); mailgun: MailGunConfiguration = new MailGunConfiguration(); + mailjet: MailJetConfiguration = new MailJetConfiguration(); } diff --git a/src/util/config/types/subconfigurations/email/MailJet.ts b/src/util/config/types/subconfigurations/email/MailJet.ts new file mode 100644 index 00000000..eccda8ac --- /dev/null +++ b/src/util/config/types/subconfigurations/email/MailJet.ts @@ -0,0 +1,22 @@ +/* + Fosscord: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Fosscord and Fosscord Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +export class MailJetConfiguration { + apiKey: string | null = null; + apiSecret: string | null = null; +} diff --git a/src/util/config/types/subconfigurations/email/index.ts b/src/util/config/types/subconfigurations/email/index.ts index 92fe9184..02cc564c 100644 --- a/src/util/config/types/subconfigurations/email/index.ts +++ b/src/util/config/types/subconfigurations/email/index.ts @@ -17,4 +17,5 @@ */ export * from "./MailGun"; +export * from "./MailJet"; export * from "./SMTP"; diff --git a/src/util/util/Email.ts b/src/util/util/Email.ts index b8019cbd..8899b3c2 100644 --- a/src/util/util/Email.ts +++ b/src/util/util/Email.ts @@ -52,45 +52,20 @@ export function adjustEmail(email?: string): string | undefined { // return email; } -export const Email: { - transporter: Transporter | null; - init: () => Promise; - initSMTP: () => Promise; - initMailgun: () => Promise; - generateVerificationLink: (id: string, email: string) => Promise; - sendVerificationEmail: (user: User, email: string) => Promise; - doReplacements: ( - template: string, - user: User, - emailVerificationUrl?: string, - passwordResetUrl?: string, - ipInfo?: { - ip: string; - city: string; - region: string; - country_name: string; - }, - ) => string; -} = { - transporter: null, - init: async function () { - const { provider } = Config.get().email; - if (!provider) return; - - if (provider === "smtp") await this.initSMTP(); - else if (provider === "mailgun") await this.initMailgun(); - else throw new Error(`Unknown email provider: ${provider}`); - }, - initSMTP: async function () { +const transporters = { + smtp: async function () { + // get configuration const { host, port, secure, username, password } = Config.get().email.smtp; + + // ensure all required configuration values are set if (!host || !port || !secure || !username || !password) return console.error( "[Email] SMTP has not been configured correctly.", ); - console.log(`[Email] Initializing SMTP transport: ${host}`); - this.transporter = nodemailer.createTransport({ + // construct the transporter + const transporter = nodemailer.createTransport({ host, port, secure, @@ -100,41 +75,117 @@ export const Email: { }, }); - await this.transporter.verify((error, _) => { - if (error) { - console.error(`[Email] SMTP error: ${error}`); - this.transporter?.close(); - this.transporter = null; - return; - } - console.log(`[Email] Ready`); + // verify connection configuration + const verified = await transporter.verify().catch((err) => { + console.error("[Email] SMTP verification failed:", err); + return; }); + + // if verification failed, return void and don't set transporter + if (!verified) return; + + return transporter; }, - initMailgun: async function () { + mailgun: async function () { + // get configuration const { apiKey, domain } = Config.get().email.mailgun; + + // ensure all required configuration values are set if (!apiKey || !domain) return console.error( "[Email] Mailgun has not been configured correctly.", ); + let mg; + try { + // try to import the transporter package + mg = require("nodemailer-mailgun-transport"); + } catch { + // if the package is not installed, log an error and return void so we don't set the transporter + console.error( + "[Email] Mailgun transport is not installed. Please run `npm install nodemailer-mailgun-transport --save-optional` to install it.", + ); + return; + } + + // create the transporter configuration object + const auth = { + auth: { + api_key: apiKey, + domain: domain, + }, + }; + + // create the transporter and return it + return nodemailer.createTransport(mg(auth)); + }, + mailjet: async function () { + // get configuration + const { apiKey, apiSecret } = Config.get().email.mailjet; + + // ensure all required configuration values are set + if (!apiKey || !apiSecret) + return console.error( + "[Email] Mailjet has not been configured correctly.", + ); + + let mj; try { - const mg = require("nodemailer-mailgun-transport"); - const auth = { - auth: { - api_key: apiKey, - domain: domain, - }, - }; - - console.log(`[Email] Initializing Mailgun transport...`); - this.transporter = nodemailer.createTransport(mg(auth)); - console.log(`[Email] Ready`); + // try to import the transporter package + mj = require("nodemailer-mailjet-transport"); } catch { + // if the package is not installed, log an error and return void so we don't set the transporter console.error( - "[Email] Mailgun transport is not installed. Please run `npm install nodemailer-mailgun-transport --save` to install it.", + "[Email] Mailjet transport is not installed. Please run `npm install nodemailer-mailjet-transport --save-optional` to install it.", ); return; } + + // create the transporter configuration object + const auth = { + auth: { + apiKey: apiKey, + apiSecret: apiSecret, + }, + }; + + // create the transporter and return it + return nodemailer.createTransport(mj(auth)); + }, +}; + +export const Email: { + transporter: Transporter | null; + init: () => Promise; + generateVerificationLink: (id: string, email: string) => Promise; + sendVerificationEmail: (user: User, email: string) => Promise; + doReplacements: ( + template: string, + user: User, + emailVerificationUrl?: string, + passwordResetUrl?: string, + ipInfo?: { + ip: string; + city: string; + region: string; + country_name: string; + }, + ) => string; +} = { + transporter: null, + init: async function () { + const { provider } = Config.get().email; + if (!provider) return; + + const transporterFn = + transporters[provider as keyof typeof transporters]; + if (!transporterFn) + return console.error(`[Email] Invalid provider: ${provider}`); + console.log(`[Email] Initializing ${provider} transport...`); + const transporter = await transporterFn(); + if (!transporter) return; + this.transporter = transporter; + console.log(`[Email] ${provider} transport initialized.`); }, /** * Replaces all placeholders in an email template with the correct values @@ -214,6 +265,7 @@ export const Email: { user.id, email, ); + // load the email template const rawTemplate = fs.readFileSync( path.join( @@ -223,13 +275,14 @@ export const Email: { ), { encoding: "utf-8" }, ); + // replace email template placeholders const html = this.doReplacements(rawTemplate, user, verificationLink); // extract the title from the email template to use as the email subject const subject = html.match(/(.*)<\/title>/)?.[1] || ""; - // // construct the email + // construct the email const message = { from: Config.get().general.correspondenceEmail || "noreply@localhost", @@ -238,7 +291,7 @@ export const Email: { html, }; - // // send the email + // send the email return this.transporter.sendMail(message); }, }; -- cgit 1.4.1