Allow self signed cert for stalwart

This commit is contained in:
Greg Kilmer
2026-07-07 22:10:17 -04:00
parent e7c5e9d66e
commit 59bcf495bf
3 changed files with 31 additions and 0 deletions
+4
View File
@@ -28,6 +28,10 @@ SMTP_USER=
SMTP_PASS= SMTP_PASS=
CONTACT_TO=contact@gregkilmer.com CONTACT_TO=contact@gregkilmer.com
SMTP_FROM="Greg's Site <no-reply@gregkilmer.com>" SMTP_FROM="Greg's Site <no-reply@gregkilmer.com>"
# Set to "true" to accept a self-signed TLS certificate on the SMTP connection.
# Only enable when the relay is on a trusted private network (e.g. the homelab
# Docker network) and presents a self-signed cert. Defaults to false (verify).
SMTP_ALLOW_SELFSIGNED=
# Optional webhook fallback (JSON POST). Only used if SMTP_HOST is unset. # Optional webhook fallback (JSON POST). Only used if SMTP_HOST is unset.
# CONTACT_WEBHOOK_URL= # CONTACT_WEBHOOK_URL=
+18
View File
@@ -25,6 +25,7 @@ delivery, set **either** SMTP vars **or** `CONTACT_WEBHOOK_URL` (see
| `SITE_URL` | Canonical URL for metadata | `https://gregkilmer.com` | | `SITE_URL` | Canonical URL for metadata | `https://gregkilmer.com` |
| `SMTP_HOST` / `SMTP_PORT` / `SMTP_USER` / `SMTP_PASS` | SMTP relay | unset | | `SMTP_HOST` / `SMTP_PORT` / `SMTP_USER` / `SMTP_PASS` | SMTP relay | unset |
| `SMTP_FROM` | From address for outgoing mail | derived from `SITE_URL` | | `SMTP_FROM` | From address for outgoing mail | derived from `SITE_URL` |
| `SMTP_ALLOW_SELFSIGNED` | Accept self-signed cert (internal hop) | `false` |
| `CONTACT_TO` | Inbox that receives submissions | `contact@gregkilmer.com` | | `CONTACT_TO` | Inbox that receives submissions | `contact@gregkilmer.com` |
| `CONTACT_WEBHOOK_URL` | JSON webhook fallback (Discord/Slack/n8n) | unset | | `CONTACT_WEBHOOK_URL` | JSON webhook fallback (Discord/Slack/n8n) | unset |
| `CONTACT_RATE_LIMIT` / `CONTACT_RATE_WINDOW_MIN` | Per-IP rate limit | `5` / `15` | | `CONTACT_RATE_LIMIT` / `CONTACT_RATE_WINDOW_MIN` | Per-IP rate limit | `5` / `15` |
@@ -123,6 +124,23 @@ not `gmail.com` — SPF on a domain you don't own will reject or sink the mail.
Test delivery + spam score with [mail-tester.com](https://www.mail-tester.com/) Test delivery + spam score with [mail-tester.com](https://www.mail-tester.com/)
(send a form submission to the unique address it gives you). (send a form submission to the unique address it gives you).
#### Self-signed certificates on an internal relay
If the SMTP relay (e.g. Stalwart) is on the same private Docker network as the
site container and presents a self-signed TLS certificate, nodemailer's default
certificate verification will reject the connection with
`Error: self-signed certificate` (`code: ESOCKET`). For a trusted internal hop
that never traverses an untrusted network, set:
```
SMTP_ALLOW_SELFSIGNED=true
```
This disables TLS certificate verification for the SMTP connection only. It is
`false` by default; only enable it when the relay is on a trusted private
network. For a relay reachable over the public internet, prefer installing a
real certificate (Let's Encrypt) on the relay instead.
### Cloudflare Turnstile (bot protection) ### Cloudflare Turnstile (bot protection)
The form includes an invisible Cloudflare Turnstile challenge. To enable it: The form includes an invisible Cloudflare Turnstile challenge. To enable it:
+9
View File
@@ -167,11 +167,20 @@ async function deliver(msg: Message): Promise<boolean> {
const nodemailer = (await import('nodemailer')).default; const nodemailer = (await import('nodemailer')).default;
const port = Number(smtpPort() ?? 587); const port = Number(smtpPort() ?? 587);
const user = smtpUser(); const user = smtpUser();
// Accept self-signed certificates when explicitly opted in. Intended for
// internal homelab hops where the SMTP relay (e.g. Stalwart on the same
// Docker network) presents a self-signed cert and the connection never
// traverses an untrusted network. Defaults to false so production-grade
// TLS verification is the default; the override is a deliberate choice.
const allowSelfSigned = env('SMTP_ALLOW_SELFSIGNED') === 'true';
const transport = nodemailer.createTransport({ const transport = nodemailer.createTransport({
host, host,
port, port,
secure: port === 465, secure: port === 465,
auth: user ? { user, pass: smtpPass() } : undefined, auth: user ? { user, pass: smtpPass() } : undefined,
...(allowSelfSigned
? { tls: { rejectUnauthorized: false } }
: {}),
}); });
const hostname = (() => { const hostname = (() => {
try { try {