Ghost + Zoho Mail SMTP: як два дні капаць не ў той бок, маючы мапу
Усяго толькі і трэба было - каб лісты са спасылкаю актывацыі акаўнтаў Ghost 6 дасылаліся новым карыстальнікам. Даменная пошта абслугоўваецца сэрвісам Zoho Mail. Уся дакументацыя ў наяўнасьці (на першы погляд). Што магло пайсьці не так? Усё правільна - што магло - усё і пайшло.
Калі дзейнічаеш па інструкцыі
Канфігурацыя зь дакументацыі - канэкт да порту 587 (STARTTLS) у /var/lib/ghost/config.production.json:
{
...
"mail": {
"transport": "SMTP",
"from": "support@varonich.online",
"options": {
"host": "smtppro.zoho.eu",
"port": 587,
"secure": false,
"auth": {
"user": "admin@varonich.online",
"pass": "<app-specific-password>"
}
},
...
}Application specific password для Zoho генеруецца менавіта ў наладках акаўнту, а не паштовага сэрвісу.
Наконт таго, якую скрыню можна выкарыстоўваць для аўтарызацыі, у дакументацыі проста сказана - павінна працаваць і так, і так:
The email address should match the email address/ email aliases of the account, for which the authentication details are provided.
Выглядае прасьцей чым два ды два скласьці. Стартую сэрвіс, спрабую рэгістраваць тэставага карыстальніка і на моманце адпраўкі ліста са спасылкай пацверджаньня ў логах назіраецца наступнае:
[2026-05-11 13:18:54] ERROR "POST /members/api/send-magic-link/" 500 346ms
Failed to send email. Reason: Message failed: 553 Sender is not allowed to relay emails.
"Please see https://ghost.org/docs/config/#mail for instructions on configuring email."
Error ID:
f5c3b8d0-4d3b-11f1-b282-7d56ec0c56a8
Error Code:
EMESSAGE
----------------------------------------
Error: Message failed: 553 Sender is not allowed to relay emails
at createMailError (/var/lib/ghost/versions/6.24.0/core/server/services/mail/ghost-mailer.js:90:12)
...
at SMTPConnection._onSocketData (/var/lib/ghost/versions/6.24.0/node_modules/nodemailer/lib/smtp-connection/index.js:195:44)
at TLSSocket.emit (node:events:519:28)
at addChunk (node:internal/streams/readable:561:12)
at readableAddChunkPushByteMode (node:internal/streams/readable:512:3)
at Readable.push (node:internal/streams/readable:392:5)
at TLSWrap.onStreamRead (node:internal/stream_base_commons:189:23) 
Мітусьня нейкая
На ўсялякі выпадак напісаў у сапорт хостэру зь пытаньнем ці дасяжны для выкарыстаньня порт 465, бо яго па-дэфолту зачыняюць, каб не злоўжывалі. Але падобна я зь гэтым ужо зьвяртаўся да іх, бо мне адказалі, што гэты порт для мяне адчынены і ўсё павінна працаваць.
Пакуль нічога незразумела, таму прымяняю эфектыўную стратэгію перабору.
— порт 587, secure: false (STARTTLS)
— порт 587, secure: false, requireTLS: true
— порт 465, secure: true (SSL/TLS)
— порт 465, secure: true, requireTLS: true
Вынік кожны раз аднолькавы: 553 Sender is not allowed to relay emails.
Зварот у сапорт Zoho дадаў толькі парады выкарыстоўваць Application specific password (а то як жа...), ніякіх памылак зь іх боку няма, ніякіх логаў ад іх сэрвісаў не паказалі.
Яшчэ раз зьвярнуўся да чароўнай істоты Загуглі - але надзіва ні ў кога падобнага кейсу не было, альбо ўсё адразу працавала ва ўсялякіх гайдах "для чайнікаў", альбо зараз неактуальная парада, што трэба выкарыстоўваць толькі галоўную паштовую скрыню. Гэтак я ўжо спрабаваў - вынік той жа, што й пры выкарыстаньні аліясаў ці наогул камбінацыі. Тобок гэта ніяк не ўплывае на працаздольнасьць.
Навобмацак
Больш пільна прагледзіўшы логі, зьвярнуў увагу, што для адпраўкі пошты Ghost выкарыстоўвае Nodemailer. Наступным крокам была спроба адправіць зь прадакт сэрверу тэставае паведамленьне зь больш дэталёвымі логамі, бо спробы актывацыі дэбаг рэжыму блёг плятформы не давалі жаданага, дакладней - зусім нічога не давалі. Накідаў просты тэставы скрыпт /tmp/test-smtp.js:
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: 'smtppro.zoho.eu',
port: 465,
secure: true,
auth: {
user: 'support@varonich.online',
pass: '******'
},
debug: true,
logger: true
});
transporter.sendMail({
from: 'Support <support@varonich.online>',
to: 'other.user@gmail.com',
subject: 'Test',
text: 'Test message'
}, (err, info) => {
if (err) {
console.error('Error:', err);
} else {
console.log('Success:', info);
}
});І запусьціў яго на сэрверы, чакаючы ўбачыць дэталі памылкі:
NODE_PATH=/var/lib/ghost/versions/6.24.0/node_modules node /tmp/test-smtp.jsАле нечакана паведамленьне пасьпяхова адправілася.
[2026-05-11 18:42:25] DEBUG Creating transport: nodemailer (6.10.1; +https://nodemailer.com/; SMTP/6.10.1[client:6.10.1])
[2026-05-11 18:42:25] DEBUG Sending mail using SMTP/6.10.1[client:6.10.1]
[2026-05-11 18:42:25] DEBUG [mQimA7O1rg4] Resolved smtppro.zoho.eu as 185.230.212.129 [cache miss]
[2026-05-11 18:42:26] INFO [mQimA7O1rg4] Secure connection established to 185.230.212.129:465
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] S: 220 mx.zoho.eu SMTP Server ready May 11, 2026 8:42:26 PM CEST
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] C: EHLO [127.0.0.1]
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] S: 250-mx.zoho.eu Hello [127.0.0.1] (static.67.143.98.91.clients.your-server.de (91.98.143.67))
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] S: 250-AUTH LOGIN PLAIN
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] S: 250 SIZE 53477376
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] SMTP handshake finished
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] C: AUTH PLAIN ***************
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] S: 235 Authentication Successful
[2026-05-11 18:42:26] INFO [mQimA7O1rg4] User "support@varonich.online" authenticated
[2026-05-11 18:42:26] INFO Sending message <00000000-0000-0000-0000-000000000000@varonich.online> to <other.user@gmail.com>
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] C: MAIL FROM:<support@varonich.online>
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] S: 250 Sender <support@varonich.online> OK
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] C: RCPT TO:<other.user@gmail.com>
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] S: 250 Recipient <other.user@gmail.com> OK
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] C: DATA
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] S: 354 Ok Send data ending with <CRLF>.<CRLF>
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] From: Support <support@varonich.online>
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] To: other.user@gmail.com
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] Subject: Test
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] Message-ID: <00000000-0000-0000-0000-000000000000@varonich.online>
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] Content-Transfer-Encoding: 7bit
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] Date: Mon, 11 May 2026 18:42:25 +0000
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] MIME-Version: 1.0
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] Content-Type: text/plain; charset=utf-8
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4]
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] Test message
[2026-05-11 18:42:26] DEBUG [mQimA7O1rg4] .
[2026-05-11 18:42:26] INFO [mQimA7O1rg4] <295 bytes encoded mime message (source size 292 bytes)>
[2026-05-11 18:42:27] DEBUG [mQimA7O1rg4] S: 250 Message received
[2026-05-11 18:42:27] DEBUG [mQimA7O1rg4] Closing connection to the server using "end"
Success: {
accepted: [ 'other.user@gmail.com' ],
rejected: [],
ehlo: [ 'AUTH LOGIN PLAIN', 'SIZE 53477376' ],
envelopeTime: 49,
messageTime: 270,
messageSize: 295,
response: '250 Message received',
envelope: { from: 'support@varonich.online', to: [ 'other.user@gmail.com' ] },
messageId: '<00000000-0000-0000-0000-000000000000@varonich.online>'
}
[2026-05-11 18:42:27] INFO [mQimA7O1rg4] Connection closed
Да такога мяне жыцьцё не рыхтавала.
Трэпанацыя мозгу
Калі апошняе звяно ў ланцугу адпрацоўвае, атрымоўваецца ламаецца на баку Ghost. Пакапаўшыся ў зыходным кодзе, знайшоў тое самае месца. На першы погляд яно не выглядала нечым падазроным, але важны кантэкст. Зараз растлумачу. У абгортцы @tryghost/nodemailer усяго толькі выпраўленьні хібаў базавага пакету @nodemailer, але пры маіх абставінах метад getFromAddress спрацаваў мінай адкладзенага дзеяньня. Пачынаючы зь таго, што ён моўчкі падмяняе поле from зь канфігурацыі на іньшае і заканчваючы тым, адкуль наогул гэта іньшае значэньня бярэцца.
Спачатку пра крыніцу гэтага сама значэньня. На маю думку размяшчэньне гэтай наладкі не самае інтуітыўнае: Settings -> Signup Portal -> Customize -> Account Page -> Support email address.

Як па мне - не самае ўвачавідкае месца і назва наладкі, каб шукаць там. Да таго ж не чакаеш што там будзе знаходзіцца радок, на які будзе няяўна мяняцца тое, што вось толькі наладзіў празь канфіг. А самае галоўнае што туды падстаўляецца. Пры ўсталёўцы плятформа блёгу бярэ дамен і дадае перад ім `noreply@`. Вось так і атрымоўваецца радок, які аказваецца непрымальным для Zoho, бо блёг працуе на дамене трэцяга ўзроўню, а пошта прывязана да другога ўзроўня. Тобок `noreply@vitaly.varonich.online` замест `support@varonich.online`.
Уся справа ў капелюшы
Пасьля замены значэньня ў гэтым полі на `support@varonich.online` усё спрацавала як сьлед і ліст са спасылкай пацверджаньня быў пасьпяхова дасланы для тэставага карыстальніка. Ghost падмяняе поле From ва ўсіх адпраўляемых лістах, выкарыстоўваючы Support Email з наладак сайта — незалежна ад таго, што ўказана ў SMTP-канфігу. Zoho адхіляе такія лісты з памылкай 553, калі From не адпавядае аўтэнтыфікаванаму дамену. У логах Ghost гэта ніяк не тлумачыцца.
Што тут можна сказаць? Па-першае - калі вы робіце публічны прадукт - варта лепей прапрацоўваць і дакументацыю, і юзабіліці, каб не даводзілася шукаць патрэбныя наладкі ў самым нечаканым месцы. Па-другое - логі: калі ўключаеш дэбаг, хочацца насамрэч бачыць максімум інфармацыі для разуменьня што адбываецца. Тут функцыя, якая была крыніцай памылкі, наогул не змяшчае логаў. І па-трэцяе - часам варта зь самага пачатку пачынаць зь разгляду зыходнага коду (калі ён дасяжны, канечне) 😃 так бывае і хутчэй і надзейней, чым спрабаваць выявіць "адкуль ногі растуць" па раскладам таро.
Беражыце свой час 😄 Рашайце праблемы эфектыўна - неэфектыўна не рашайце. Да новых сустрэч.