From c848801268ef22bb438faff2aab07c0820f71070 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?=
Date: Mon, 2 Feb 2026 13:36:38 -0500
Subject: [PATCH] feat: closes #13009, add dedicated test smtp button
which uses the dirty settings on the page
add clarification under send test email button
add missing lang keys
---
.../language/en-GB/admin/settings/email.json | 8 +-
public/src/admin/settings/email.js | 26 +++++-
src/controllers/admin/settings.js | 6 ++
src/emailer.js | 85 ++++++++++---------
src/socket.io/admin/email.js | 24 ++++++
src/views/admin/settings/email.tpl | 18 +++-
6 files changed, 121 insertions(+), 46 deletions(-)
diff --git a/public/language/en-GB/admin/settings/email.json b/public/language/en-GB/admin/settings/email.json
index 0310939cb3..c7a3628a7f 100644
--- a/public/language/en-GB/admin/settings/email.json
+++ b/public/language/en-GB/admin/settings/email.json
@@ -30,14 +30,20 @@
"smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.",
"smtp-transport.allow-self-signed": "Allow self-signed certificates",
"smtp-transport.allow-self-signed-help": "Enabling this setting will allow you to use self-signed or invalid TLS certificates.",
+ "smtp-transport.test-success": "SMTP Test email sent successfully.",
"template": "Edit Email Template",
"template.select": "Select Email Template",
"template.revert": "Revert to Original",
+ "test-smtp-settings": "Test SMTP Settings",
"testing": "Email Testing",
+ "testing.success": "Test Email Sent.",
"testing.select": "Select Email Template",
"testing.send": "Send Test Email",
- "testing.send-help": "The test email will be sent to the currently logged in user's email address.",
+ "testing.send-help-plugin": "\"%1\" will be used to send test emails.",
+ "testing.send-help-smtp": "SMTP transport is enabled and will be used to send emails.",
+ "testing.send-help-no-plugin": "No emailer plugin is installed to send emails, nodemailer will be used by default.",
+ "testing.send-help": "The test email will be sent to the currently logged in user's email address using the saved settings on this page. ",
"subscriptions": "Email Digests",
"subscriptions.disable": "Disable email digests",
"subscriptions.hour": "Digest Hour",
diff --git a/public/src/admin/settings/email.js b/public/src/admin/settings/email.js
index d514f2a482..83276844f4 100644
--- a/public/src/admin/settings/email.js
+++ b/public/src/admin/settings/email.js
@@ -6,6 +6,7 @@ define('admin/settings/email', ['ace/ace', 'alerts', 'admin/settings'], function
let emailEditor;
module.init = function () {
+ configureSmtpTester();
configureEmailTester();
configureEmailEditor();
handleDigestHourChange();
@@ -26,6 +27,29 @@ define('admin/settings/email', ['ace/ace', 'alerts', 'admin/settings'], function
socket.emit('admin.user.restartJobs');
}
+ function configureSmtpTester() {
+ $('[data-action="email.smtp.test"]').on('click', function () {
+ const smtpOptions = {};
+ $('[data-field^="email:smtp"]').each(function (index, el) {
+ const $el = $(el);
+ const key = $el.attr('data-field');
+ if ($el.is(':checkbox')) {
+ smtpOptions[key] = $el.is(':checked');
+ } else {
+ smtpOptions[key] = $el.val();
+ }
+ });
+
+ socket.emit('admin.email.testSmtp', { smtp: smtpOptions }, function (err) {
+ if (err) {
+ console.error(err.message);
+ return alerts.error(err);
+ }
+ alerts.success('[[admin/settings/email:smtp-transport.test-success]]');
+ });
+ });
+ }
+
function configureEmailTester() {
$('button[data-action="email.test"]').off('click').on('click', function () {
socket.emit('admin.email.test', { template: $('#test-email').val() }, function (err) {
@@ -33,7 +57,7 @@ define('admin/settings/email', ['ace/ace', 'alerts', 'admin/settings'], function
console.error(err.message);
return alerts.error(err);
}
- alerts.success('Test Email Sent');
+ alerts.success('[[admin/settings/email:testing.success]]');
});
return false;
});
diff --git a/src/controllers/admin/settings.js b/src/controllers/admin/settings.js
index 184c6c0ed6..f27ecbae67 100644
--- a/src/controllers/admin/settings.js
+++ b/src/controllers/admin/settings.js
@@ -14,6 +14,7 @@ const api = require('../../api');
const pagination = require('../../pagination');
const helpers = require('../helpers');
const translator = require('../../translator');
+const plugins = require('../../plugins');
const settingsController = module.exports;
@@ -114,9 +115,14 @@ settingsController.uploads = async (req, res) => {
settingsController.email = async (req, res) => {
const emails = await emailer.getTemplates(meta.config);
+ const hooks = plugins.loadedHooks['static:email.send'];
+ const emailerPlugin = hooks && hooks.length ? hooks[0].id : null;
+ const smtpEnabled = parseInt(meta.config['email:smtpTransport:enabled'], 10) === 1;
res.render('admin/settings/email', {
title: '[[admin/menu:settings/email]]',
+ emailerPlugin,
+ smtpEnabled,
emails: emails,
sendable: emails.filter(e => !e.path.includes('_plaintext') && !e.path.includes('partials')).map(tpl => tpl.path),
services: emailer.listServices(),
diff --git a/src/emailer.js b/src/emailer.js
index ad7e762916..38d8867fa2 100644
--- a/src/emailer.js
+++ b/src/emailer.js
@@ -56,8 +56,7 @@ const smtpSettingsChanged = (config) => {
const getHostname = () => {
const configUrl = nconf.get('url');
- const parsed = url.parse(configUrl);
- return parsed.hostname;
+ return new URL(configUrl).hostname;
};
const buildCustomTemplates = async (config) => {
@@ -120,51 +119,55 @@ Emailer.setupFallbackTransport = (config) => {
winston.verbose('[emailer] Setting up fallback transport');
// Enable SMTP transport if enabled in ACP
if (parseInt(config['email:smtpTransport:enabled'], 10) === 1) {
- const smtpOptions = {
- name: getHostname(),
- pool: config['email:smtpTransport:pool'],
- };
-
- if (config['email:smtpTransport:user'] || config['email:smtpTransport:pass']) {
- smtpOptions.auth = {
- user: config['email:smtpTransport:user'],
- pass: config['email:smtpTransport:pass'],
- };
- }
-
- if (config['email:smtpTransport:service'] === 'nodebb-custom-smtp') {
- smtpOptions.port = config['email:smtpTransport:port'];
- smtpOptions.host = config['email:smtpTransport:host'];
-
- if (config['email:smtpTransport:security'] === 'NONE') {
- smtpOptions.secure = false;
- smtpOptions.requireTLS = false;
- smtpOptions.ignoreTLS = true;
- } else if (config['email:smtpTransport:security'] === 'STARTTLS') {
- smtpOptions.secure = false;
- smtpOptions.requireTLS = true;
- smtpOptions.ignoreTLS = false;
- } else {
- // meta.config['email:smtpTransport:security'] === 'ENCRYPTED' or undefined
- smtpOptions.secure = true;
- smtpOptions.requireTLS = true;
- smtpOptions.ignoreTLS = false;
- }
- } else {
- smtpOptions.service = String(config['email:smtpTransport:service']);
- }
- if (config['email:smtpTransport:allow-self-signed']) {
- smtpOptions.tls = {
- rejectUnauthorized: false,
- };
- }
- Emailer.transports.smtp = nodemailer.createTransport(smtpOptions);
+ Emailer.transports.smtp = Emailer.createSmtpTransport(config);
Emailer.fallbackTransport = Emailer.transports.smtp;
} else {
Emailer.fallbackTransport = Emailer.transports.sendmail;
}
};
+Emailer.createSmtpTransport = (config) => {
+ const smtpOptions = {
+ name: getHostname(),
+ pool: config['email:smtpTransport:pool'],
+ };
+
+ if (config['email:smtpTransport:user'] || config['email:smtpTransport:pass']) {
+ smtpOptions.auth = {
+ user: config['email:smtpTransport:user'],
+ pass: config['email:smtpTransport:pass'],
+ };
+ }
+
+ if (config['email:smtpTransport:service'] === 'nodebb-custom-smtp') {
+ smtpOptions.port = config['email:smtpTransport:port'];
+ smtpOptions.host = config['email:smtpTransport:host'];
+
+ if (config['email:smtpTransport:security'] === 'NONE') {
+ smtpOptions.secure = false;
+ smtpOptions.requireTLS = false;
+ smtpOptions.ignoreTLS = true;
+ } else if (config['email:smtpTransport:security'] === 'STARTTLS') {
+ smtpOptions.secure = false;
+ smtpOptions.requireTLS = true;
+ smtpOptions.ignoreTLS = false;
+ } else {
+ // meta.config['email:smtpTransport:security'] === 'ENCRYPTED' or undefined
+ smtpOptions.secure = true;
+ smtpOptions.requireTLS = true;
+ smtpOptions.ignoreTLS = false;
+ }
+ } else {
+ smtpOptions.service = String(config['email:smtpTransport:service']);
+ }
+ if (config['email:smtpTransport:allow-self-signed']) {
+ smtpOptions.tls = {
+ rejectUnauthorized: false,
+ };
+ }
+ return nodemailer.createTransport(smtpOptions);
+};
+
Emailer.registerApp = (expressApp) => {
app = expressApp;
diff --git a/src/socket.io/admin/email.js b/src/socket.io/admin/email.js
index b2a160b1f3..9059520b2b 100644
--- a/src/socket.io/admin/email.js
+++ b/src/socket.io/admin/email.js
@@ -1,5 +1,6 @@
'use strict';
+const nconf = require('nconf');
const winston = require('winston');
const meta = require('../../meta');
@@ -8,6 +9,7 @@ const userEmail = require('../../user/email');
const notifications = require('../../notifications');
const emailer = require('../../emailer');
const utils = require('../../utils');
+const user = require('../../user');
const Email = module.exports;
@@ -72,3 +74,25 @@ Email.test = async function (socket, data) {
throw err;
}
};
+
+Email.testSmtp = async (socket, data) => {
+ try {
+ const smtp = emailer.createSmtpTransport(data.smtp);
+ const content = 'This is a test email sent from NodeBB to verify your SMTP settings are correct.';
+ const { hostname } = new URL(nconf.get('url'));
+ const toEmail = await user.getUserField(socket.uid, 'email');
+ await smtp.sendMail({
+ to: toEmail,
+ subject: `[${meta.config.title}] SMTP Settings Test Email`,
+ html: content,
+ text: content,
+ from: {
+ name: meta.config['email:from_name'] || 'NodeBB',
+ address: meta.config['email:from'] || `no-reply@${hostname}`,
+ },
+ });
+ } catch (err) {
+ winston.error(err.stack);
+ throw err;
+ }
+};
\ No newline at end of file
diff --git a/src/views/admin/settings/email.tpl b/src/views/admin/settings/email.tpl
index d39d3b8f72..5630d2aad5 100644
--- a/src/views/admin/settings/email.tpl
+++ b/src/views/admin/settings/email.tpl
@@ -167,10 +167,13 @@
[[admin/settings/email:smtp-transport.username-help]]
-
+
+
+
+
@@ -181,14 +184,23 @@
- [[admin/settings/email:testing.send-help]]
+ [[admin/settings/email:testing.send-help]]
+ {{{ if emailerPlugin }}}
+ [[admin/settings/email:testing.send-help-plugin, {emailerPlugin}]]
+ {{{ else }}}
+ {{{ if smtpEnabled }}}
+ [[admin/settings/email:testing.send-help-smtp]]
+ {{{ else }}}
+ [[admin/settings/email:testing.send-help-no-plugin]]
+ {{{ end }}}
+ {{{ end }}}