127 lines
4.9 KiB
Bash
127 lines
4.9 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# Outbound email for Thermograph — run once on the VPS, as root.
|
||
|
|
#
|
||
|
|
# Installs Postfix as a SEND-ONLY NULL CLIENT: it listens on 127.0.0.1:25 only,
|
||
|
|
# accepts mail from this machine, and never receives mail from the internet.
|
||
|
|
#
|
||
|
|
# Why a local MTA instead of talking to a mail provider's API from Python:
|
||
|
|
#
|
||
|
|
# * The app's only mail config becomes "SMTP on localhost". Whether delivery
|
||
|
|
# then goes direct to the recipient's MX or through a relay is a Postfix
|
||
|
|
# setting — switchable without touching, redeploying, or retesting the app.
|
||
|
|
# * Postfix queues and retries. A request handler hands the message over in
|
||
|
|
# microseconds and returns; a slow or briefly-down upstream can't stall a
|
||
|
|
# web request or lose a signup.
|
||
|
|
# * No new Python dependency: stdlib smtplib talks to it (see backend/mailer.py).
|
||
|
|
#
|
||
|
|
# DELIVERABILITY — read before pointing this at real subscribers.
|
||
|
|
#
|
||
|
|
# Mail sent straight from a VPS IP is very often junked, regardless of Postfix
|
||
|
|
# config, because the IP has no sending reputation. Two options:
|
||
|
|
#
|
||
|
|
# A. RELAY through a transactional provider (recommended for real mail).
|
||
|
|
# Set RELAYHOST + RELAY_USER + RELAY_PASSWORD below. The provider handles
|
||
|
|
# SPF/DKIM alignment and reputation; you keep the loopback-SMTP seam.
|
||
|
|
#
|
||
|
|
# B. DIRECT to MX (no third party). Then you must also set up, in DNS:
|
||
|
|
# - SPF: TXT @ "v=spf1 a mx ip4:<VPS_IP> -all"
|
||
|
|
# - DKIM: install opendkim, publish the public key as a TXT record
|
||
|
|
# - DMARC: TXT _dmarc "v=DMARC1; p=none; rua=mailto:you@domain"
|
||
|
|
# - PTR / reverse DNS on the VPS IP -> mail.thermograph.org
|
||
|
|
# The PTR record is the one people forget, and its absence alone is enough
|
||
|
|
# for Gmail and Outlook to junk everything you send.
|
||
|
|
#
|
||
|
|
# Usage:
|
||
|
|
# sudo MAIL_DOMAIN=thermograph.org bash deploy/provision-mail.sh
|
||
|
|
# sudo MAIL_DOMAIN=thermograph.org RELAYHOST='[smtp.provider.com]:587' \
|
||
|
|
# RELAY_USER=apikey RELAY_PASSWORD=secret bash deploy/provision-mail.sh
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
MAIL_DOMAIN="${MAIL_DOMAIN:-thermograph.org}"
|
||
|
|
MAIL_HOSTNAME="${MAIL_HOSTNAME:-mail.${MAIL_DOMAIN}}"
|
||
|
|
RELAYHOST="${RELAYHOST:-}"
|
||
|
|
RELAY_USER="${RELAY_USER:-}"
|
||
|
|
RELAY_PASSWORD="${RELAY_PASSWORD:-}"
|
||
|
|
|
||
|
|
if [[ $EUID -ne 0 ]]; then
|
||
|
|
echo "run as root (sudo)" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "==> installing postfix (non-interactive)"
|
||
|
|
export DEBIAN_FRONTEND=noninteractive
|
||
|
|
# Preseed so the installer doesn't open its curses dialog.
|
||
|
|
debconf-set-selections <<EOF
|
||
|
|
postfix postfix/main_mailer_type select Internet Site
|
||
|
|
postfix postfix/mailname string ${MAIL_HOSTNAME}
|
||
|
|
EOF
|
||
|
|
apt-get update -qq
|
||
|
|
apt-get install -y -qq postfix libsasl2-modules
|
||
|
|
|
||
|
|
echo "==> configuring send-only null client"
|
||
|
|
postconf -e "myhostname = ${MAIL_HOSTNAME}"
|
||
|
|
postconf -e "myorigin = ${MAIL_DOMAIN}"
|
||
|
|
# THE important line: never listen on a public interface. This box sends only.
|
||
|
|
postconf -e "inet_interfaces = loopback-only"
|
||
|
|
postconf -e "inet_protocols = ipv4"
|
||
|
|
# Accept mail only from this machine.
|
||
|
|
postconf -e "mynetworks = 127.0.0.0/8 [::1]/128"
|
||
|
|
# A null client delivers nothing locally; everything is relayed out.
|
||
|
|
postconf -e "mydestination ="
|
||
|
|
postconf -e "local_transport = error:local delivery is disabled"
|
||
|
|
# Use TLS opportunistically when talking to the next hop.
|
||
|
|
postconf -e "smtp_tls_security_level = may"
|
||
|
|
postconf -e "smtp_tls_loglevel = 1"
|
||
|
|
|
||
|
|
if [[ -n "$RELAYHOST" ]]; then
|
||
|
|
echo "==> configuring relay via ${RELAYHOST}"
|
||
|
|
postconf -e "relayhost = ${RELAYHOST}"
|
||
|
|
if [[ -n "$RELAY_USER" ]]; then
|
||
|
|
postconf -e "smtp_sasl_auth_enable = yes"
|
||
|
|
postconf -e "smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd"
|
||
|
|
postconf -e "smtp_sasl_security_options = noanonymous"
|
||
|
|
printf '%s %s:%s\n' "$RELAYHOST" "$RELAY_USER" "$RELAY_PASSWORD" \
|
||
|
|
> /etc/postfix/sasl_passwd
|
||
|
|
# The credential file must not be world-readable.
|
||
|
|
chmod 600 /etc/postfix/sasl_passwd
|
||
|
|
postmap /etc/postfix/sasl_passwd
|
||
|
|
chmod 600 /etc/postfix/sasl_passwd.db
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
echo "==> no RELAYHOST set: delivering direct to MX"
|
||
|
|
echo " remember SPF + DKIM + DMARC + PTR, or expect the spam folder"
|
||
|
|
postconf -e "relayhost ="
|
||
|
|
fi
|
||
|
|
|
||
|
|
systemctl enable postfix
|
||
|
|
systemctl restart postfix
|
||
|
|
|
||
|
|
echo "==> verifying it listens on loopback only"
|
||
|
|
ss -lntp | grep ':25 ' || true
|
||
|
|
|
||
|
|
cat <<'NOTE'
|
||
|
|
|
||
|
|
==> next steps
|
||
|
|
|
||
|
|
1. Point the app at it, in /etc/thermograph.env:
|
||
|
|
|
||
|
|
THERMOGRAPH_MAIL_BACKEND=smtp
|
||
|
|
THERMOGRAPH_SMTP_HOST=127.0.0.1
|
||
|
|
THERMOGRAPH_SMTP_PORT=25
|
||
|
|
THERMOGRAPH_MAIL_FROM=Thermograph <no-reply@thermograph.org>
|
||
|
|
|
||
|
|
then: sudo systemctl restart thermograph
|
||
|
|
|
||
|
|
2. Send yourself a test message:
|
||
|
|
|
||
|
|
echo "test body" | mail -s "thermograph test" you@example.com
|
||
|
|
# or, exercising the app's own path:
|
||
|
|
# python -c "import sys; sys.path.insert(0,'/opt/thermograph/backend'); \
|
||
|
|
# import mailer; print(mailer.send('you@example.com','t','body'))"
|
||
|
|
|
||
|
|
3. Watch it leave: journalctl -u postfix -f (queue: mailq)
|
||
|
|
|
||
|
|
4. Check placement with https://www.mail-tester.com — it scores SPF, DKIM,
|
||
|
|
DMARC and rDNS in one shot and tells you exactly what's missing.
|
||
|
|
NOTE
|