diff --git a/package-lock.json b/package-lock.json index a07a7ec7..c6960e6c 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 8a6c0405..819b040a 100644 --- a/package.json +++ b/package.json @@ -116,6 +116,7 @@ "optionalDependencies": { "erlpack": "^0.1.4", "sqlite3": "^5.1.4", - "nodemailer-mailgun-transport": "^2.1.5" + "nodemailer-mailgun-transport": "^2.1.5", + "nodemailer-mailjet-transport": "^1.0.4" } } 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,11 +52,111 @@ export function adjustEmail(email?: string): string | undefined { // return email; } +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.", + ); + + // construct the transporter + const transporter = nodemailer.createTransport({ + host, + port, + secure, + auth: { + user: username, + pass: password, + }, + }); + + // 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; + }, + 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 { + // 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] 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; - initSMTP: () => Promise; - initMailgun: () => Promise; generateVerificationLink: (id: string, email: string) => Promise; sendVerificationEmail: (user: User, email: string) => Promise; doReplacements: ( @@ -77,64 +177,15 @@ export const Email: { 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 { host, port, secure, username, password } = - Config.get().email.smtp; - 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({ - host, - port, - secure, - auth: { - user: username, - pass: password, - }, - }); - - await this.transporter.verify((error, _) => { - if (error) { - console.error(`[Email] SMTP error: ${error}`); - this.transporter?.close(); - this.transporter = null; - return; - } - console.log(`[Email] Ready`); - }); - }, - initMailgun: async function () { - const { apiKey, domain } = Config.get().email.mailgun; - if (!apiKey || !domain) - return console.error( - "[Email] Mailgun has not been configured correctly.", - ); - - 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`); - } catch { - console.error( - "[Email] Mailgun transport is not installed. Please run `npm install nodemailer-mailgun-transport --save` to install it.", - ); - 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); }, };