From 088e7d89ab2e875a6423e3958bd54e13655c6653 Mon Sep 17 00:00:00 2001 From: Rory& Date: Tue, 30 Sep 2025 04:22:33 +0200 Subject: [PATCH] Port SMTP transport, make nodemailer optional --- package.json | 2 +- src/util/util/email/clients/SMTPTransport.ts | 63 +++++++++++++++++++ .../util/email/clients/SendGridEmailClient.ts | 20 +++--- src/util/util/email/index.ts | 3 +- src/util/util/email/transports/SMTP.ts | 59 ----------------- src/util/util/email/transports/index.ts | 19 ------ 6 files changed, 76 insertions(+), 90 deletions(-) create mode 100644 src/util/util/email/clients/SMTPTransport.ts delete mode 100644 src/util/util/email/transports/SMTP.ts delete mode 100644 src/util/util/email/transports/index.ts diff --git a/package.json b/package.json index 5d59c524..fc7bb8dd 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,6 @@ "node-2fa": "^2.0.3", "node-fetch-commonjs": "^3.3.2", "node-os-utils": "^1.3.7", - "nodemailer": "^7.0.6", "picocolors": "^1.1.1", "probe-image-size": "^7.2.3", "proxy-agent": "^6.5.0", @@ -127,6 +126,7 @@ "@yukikaze-bot/erlpack": "^1.0.1", "jimp": "^1.6.0", "mysql": "^2.18.1", + "nodemailer": "^7.0.6", "nodemailer-mailgun-transport": "^2.1.5", "nodemailer-mailjet-transport": "github:n0script22/nodemailer-mailjet-transport", "pg": "^8.16.3", diff --git a/src/util/util/email/clients/SMTPTransport.ts b/src/util/util/email/clients/SMTPTransport.ts new file mode 100644 index 00000000..828c4a3e --- /dev/null +++ b/src/util/util/email/clients/SMTPTransport.ts @@ -0,0 +1,63 @@ +import { BaseEmailClient, IEmail } from "./IEmailClient"; +import { Config } from "@spacebar/util*"; + +export class SMTPTransport extends BaseEmailClient { + // sendGrid?: unknown; + nodemailer?: typeof import("nodemailer"); + transporter: import("nodemailer").Transporter; + override async init(): Promise { + try { + // try to import the transporter package + this.nodemailer = (await import("nodemailer")).default; + } catch { + // if the package is not installed, log an error and return void so we don't set the transporter + console.error("[Email] nodemailer is not installed. Please run `npm install nodemailer --save-optional` to install it."); + return; + } + // get configuration + const { host, port, secure, username, password } = Config.get().email.smtp; + + // ensure all required configuration values are set + if (!host || !port || secure === null || !username || !password) return console.error("[Email] SMTP has not been configured correctly."); + + if (!Config.get().email.senderAddress && !Config.get().general.correspondenceEmail) + return console.error( + '[Email] You have to configure either "email_senderAddress" or "general_correspondenceEmail" for emails to work. The configured value is used as the sender address.', + ); + + // construct the transporter + const transporter = this.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; + + this.transporter = transporter; + } + + override async sendMail(email: IEmail): Promise { + if (!this.nodemailer) throw new Error("nodemailer not initialized"); + if (!this.transporter) throw new Error("nodemailer transporter not initialized"); + + await this.transporter.sendMail({ + to: email.to, + from: email.from, + subject: email.subject, + text: email.text, + html: email.html, + }); + } +} diff --git a/src/util/util/email/clients/SendGridEmailClient.ts b/src/util/util/email/clients/SendGridEmailClient.ts index cb30ea8b..e3006e49 100644 --- a/src/util/util/email/clients/SendGridEmailClient.ts +++ b/src/util/util/email/clients/SendGridEmailClient.ts @@ -2,34 +2,34 @@ import { BaseEmailClient, IEmail } from "./IEmailClient"; import { Config } from "@spacebar/util*"; export class SendGridEmailClient extends BaseEmailClient { - // sendGrid?: unknown; - sendGrid?: typeof import("@sendgrid/mail"); + sendGrid?: unknown; + // sendGrid?: typeof import("@sendgrid/mail"); // for development - doesn't work if package isn't installed override async init(): Promise { // get configuration const { apiKey } = Config.get().email.sendgrid; // ensure all required configuration values are set - if (!apiKey) - return console.error( - "[Email] SendGrid has not been configured correctly.", - ); + if (!apiKey) return console.error("[Email] SendGrid has not been configured correctly."); try { // try to import the transporter package this.sendGrid = (await import("@sendgrid/mail")).default; } catch { // if the package is not installed, log an error and return void so we don't set the transporter - console.error( - "[Email] SendGrid transport is not installed. Please run `npm install Maria-Golomb/nodemailer-sendgrid-transport --save-optional` to install it.", - ); + console.error("[Email] SendGrid transport is not installed. Please run `npm install @sendgrid/mail --save-optional` to install it."); return; } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error this.sendGrid.setApiKey(apiKey); } override async sendMail(email: IEmail): Promise { if (!this.sendGrid) throw new Error("SendGrid not initialized"); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error await this.sendGrid.send({ to: email.to, from: email.from, @@ -38,4 +38,4 @@ export class SendGridEmailClient extends BaseEmailClient { html: email.html, }); } -} \ No newline at end of file +} diff --git a/src/util/util/email/index.ts b/src/util/util/email/index.ts index 64acc3cc..8223fa55 100644 --- a/src/util/util/email/index.ts +++ b/src/util/util/email/index.ts @@ -24,6 +24,7 @@ import { Config } from "../Config"; import { generateToken } from "../Token"; import { BaseEmailClient, IEmail, IEmailClient } from "./clients/IEmailClient"; import { SendGridEmailClient } from "./clients/SendGridEmailClient"; +import { SMTPTransport } from "./clients/SMTPTransport"; const ASSET_FOLDER_PATH = path.join( __dirname, @@ -78,7 +79,7 @@ export const Email: { switch (provider) { case "smtp": - this.transporter = new BaseEmailClient(); + this.transporter = new SMTPTransport(); break; case "sendgrid": this.transporter = new SendGridEmailClient(); diff --git a/src/util/util/email/transports/SMTP.ts b/src/util/util/email/transports/SMTP.ts deleted file mode 100644 index e3031943..00000000 --- a/src/util/util/email/transports/SMTP.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - Spacebar: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2023 Spacebar and Spacebar 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 . -*/ - -import { Config } from "@spacebar/util"; -import nodemailer from "nodemailer"; - -export default 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 === null || !username || !password) - return console.error("[Email] SMTP has not been configured correctly."); - - if ( - !Config.get().email.senderAddress && - !Config.get().general.correspondenceEmail - ) - return console.error( - '[Email] You have to configure either "email_senderAddress" or "general_correspondenceEmail" for emails to work. The configured value is used as the sender address.', - ); - - // 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; -} diff --git a/src/util/util/email/transports/index.ts b/src/util/util/email/transports/index.ts deleted file mode 100644 index 4d40f9cc..00000000 --- a/src/util/util/email/transports/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - Spacebar: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2023 Spacebar and Spacebar 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 * from "./SMTP";