diff --git a/public/language/en-GB/admin/dashboard.json b/public/language/en-GB/admin/dashboard.json index b5ff928dd4..0de31d4917 100644 --- a/public/language/en-GB/admin/dashboard.json +++ b/public/language/en-GB/admin/dashboard.json @@ -30,6 +30,7 @@ "upgrade-available": "

A new version (v%1) has been released. Consider upgrading your NodeBB.

", "prerelease-upgrade-available": "

This is an outdated pre-release version of NodeBB. A new version (v%1) has been released. Consider upgrading your NodeBB.

", "prerelease-warning": "

This is a pre-release version of NodeBB. Unintended bugs may occur.

", + "fallback-emailer-not-found": "Fallback emailer not found!", "running-in-development": "Forum is running in development mode. The forum may be open to potential vulnerabilities; please contact your system administrator.", "latest-lookup-failed": "

Failed to look up latest available version of NodeBB

", diff --git a/src/controllers/admin/dashboard.js b/src/controllers/admin/dashboard.js index 3f85be56a1..bf47cb9f58 100644 --- a/src/controllers/admin/dashboard.js +++ b/src/controllers/admin/dashboard.js @@ -13,6 +13,7 @@ const plugins = require('../../plugins'); const user = require('../../user'); const topics = require('../../topics'); const utils = require('../../utils'); +const emailer = require('../../emailer'); const dashboardController = module.exports; @@ -56,6 +57,13 @@ async function getNotices() { }, ]; + if (emailer.fallbackNotFound) { + notices.push({ + done: false, + notDoneText: '[[admin/dashboard:fallback-emailer-not-found]]', + }); + } + if (global.env !== 'production') { notices.push({ done: false, diff --git a/src/emailer.js b/src/emailer.js index e95fa612fd..012fed1d4c 100644 --- a/src/emailer.js +++ b/src/emailer.js @@ -25,6 +25,8 @@ const Emailer = module.exports; let prevConfig; let app; +Emailer.fallbackNotFound = false; + Emailer.transports = { sendmail: nodemailer.createTransport({ sendmail: true, @@ -312,7 +314,8 @@ Emailer.sendToEmail = async (template, email, language, params) => { headers: params.headers, rtl: params.rtl, }); - + const usingFallback = !Plugins.hooks.hasListeners('filter:email.send') && + !Plugins.hooks.hasListeners('static:email.send'); try { if (Plugins.hooks.hasListeners('filter:email.send')) { // Deprecated, remove in v1.18.0 @@ -323,7 +326,8 @@ Emailer.sendToEmail = async (template, email, language, params) => { await Emailer.sendViaFallback(data); } } catch (err) { - if (err && err.code === 'ENOENT') { + if (err.code === 'ENOENT' && usingFallback) { + Emailer.fallbackNotFound = true; throw new Error('[[error:sendmail-not-found]]'); } else { throw err; diff --git a/src/notifications.js b/src/notifications.js index 4481e0cece..6a31e1b09a 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -186,6 +186,7 @@ async function pushToUids(uids, notification) { } body = posts.relativeToAbsolute(body, posts.urlRegex); body = posts.relativeToAbsolute(body, posts.imgRegex); + let errorLogged = false; await async.eachLimit(uids, 3, async (uid) => { await emailer.send('notification', uid, { path: notification.path, @@ -195,7 +196,12 @@ async function pushToUids(uids, notification) { body: body, notification: notification, showUnsubscribe: true, - }).catch(err => winston.error(`[emailer.send] ${err.stack}`)); + }).catch((err) => { + if (!errorLogged) { + winston.error(`[emailer.send] ${err.stack}`); + errorLogged = true; + } + }); }); } diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 3609f4b55e..5012818dc2 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -86,10 +86,14 @@ User.sendValidationEmail = async function (socket, uids) { } const failed = []; - + let errorLogged = false; await async.eachLimit(uids, 50, async (uid) => { await user.email.sendValidationEmail(uid, { force: true }).catch((err) => { - winston.error(`[user.create] Validation email failed to send\n[emailer.send] ${err.stack}`); + if (!errorLogged) { + winston.error(`[user.create] Validation email failed to send\n[emailer.send] ${err.stack}`); + errorLogged = true; + } + failed.push(uid); }); }); diff --git a/src/user/digest.js b/src/user/digest.js index 885dfd69c4..dec6a43df3 100644 --- a/src/user/digest.js +++ b/src/user/digest.js @@ -100,7 +100,7 @@ Digest.send = async function (data) { if (!data || !data.subscribers || !data.subscribers.length) { return emailsSent; } - + let errorLogged = false; await batch.processArray(data.subscribers, async (uids) => { let userData = await user.getUsersFields(uids, ['uid', 'email', 'email:confirmed', 'username', 'userslug', 'lastonline']); userData = userData.filter(u => u && u.email && (!meta.config.requireEmailConfirmation || u['email:confirmed'])); @@ -139,7 +139,12 @@ Digest.send = async function (data) { popularTopics: topics.popular, interval: data.interval, showUnsubscribe: true, - }).catch(err => winston.error(`[user/jobs] Could not send digest email\n[emailer.send] ${err.stack}`)); + }).catch((err) => { + if (!errorLogged) { + winston.error(`[user/jobs] Could not send digest email\n[emailer.send] ${err.stack}`); + errorLogged = true; + } + }); })); if (data.interval !== 'alltime') { const now = Date.now();