This runbook configures the daily rgbdns-query-report.service to deliver
through Resend SMTP. It covers two
sendmail-compatible transports:
msmtp, a small SMTP client; and- Sendmail itself, configured as an authenticated Resend smarthost.
The report script does not speak Resend's HTTP API directly. It writes a
complete message to the executable named by REPORT_SENDMAIL and invokes it
with -t:
# /etc/rgbdns/query-report.env
REPORT_TO=deliverable@gmail.com
REPORT_FROM=rgbdns@hurz.net
REPORT_SENDMAIL=/usr/sbin/sendmail
REPORT_FROM must use a domain verified in Resend. The hostname of the DNS
server, such as a.ns.cron.sh, does not need to be the mail sender domain.
In Resend:
- Add and verify the sender domain, for example
hurz.net. - Publish every DNS record Resend provides. The DKIM record commonly has the
name
resend._domainkeyunder the domain. - Create a sending API key. Treat it as a password and never commit it.
The Resend SMTP settings are:
Host: smtp.resend.com
Port: 587
Security: STARTTLS
Username: resend
Password: the Resend API key
Resend also documents ports 465/2465 for implicit TLS and 25/2587 for STARTTLS. Port 587 is used in the examples below.
Verify the DKIM record from the server or any machine with dig:
dig +short CNAME resend._domainkey.hurz.net
dig @1.1.1.1 +short CNAME resend._domainkey.hurz.net
dig @8.8.8.8 +short CNAME resend._domainkey.hurz.netThe returned CNAME target must exactly match the value shown in the Resend
dashboard. A DKIM record alone does not authorize a different From: domain;
the message sender must still use the verified domain.
On the primary only, edit the report configuration:
sudo vi /etc/rgbdns/query-report.envUse a verified sender address:
REPORT_TO=deliverable@gmail.com
REPORT_FROM=rgbdns@hurz.net
The report timer is intentionally opt-in and should normally run only on the primary, not on the secondary:
sudo systemctl daemon-reload
sudo systemctl enable --now rgbdns-query-report.timer
systemctl list-timers --all rgbdns-query-report.timerThe timer is not the mail transport. It only starts the report service. A manual run sends the preceding local calendar day's report immediately:
sudo systemctl reset-failed rgbdns-query-report.service
sudo systemctl start rgbdns-query-report.service
sudo systemctl status rgbdns-query-report.service --no-pagerUse msmtp when the host only needs to submit outbound messages through a
smarthost. It is small, has a direct SMTP configuration, and avoids managing a
full MTA queue, daemon, hostname identity, and Sendmail macro configuration.
This is generally the simplest choice for one daily report.
Install the client on Debian/Ubuntu:
sudo apt-get update
sudo apt-get install msmtpCreate the system configuration:
sudo vi /etc/msmtprcUse the actual Resend API key in place of the placeholder:
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
account resend
host smtp.resend.com
port 587
user resend
password re_YOUR_RESEND_API_KEY
from rgbdns@hurz.net
account default : resend
Protect the key:
sudo chmod 600 /etc/msmtprcPoint the report at msmtp:
sudo vi /etc/rgbdns/query-report.envREPORT_SENDMAIL=/usr/bin/msmtp
REPORT_FROM=rgbdns@hurz.net
The report already supplies To:, From:, Subject:, and the message body,
so msmtp -t can read the complete message from standard input.
Send a small test message without putting the API key on the command line:
printf 'From: rgbdns@hurz.net\nTo: deliverable@gmail.com\nSubject: Resend test\n\nSMTP test\n' |
sudo /usr/bin/msmtp -t -vThen run the report service:
sudo systemctl reset-failed rgbdns-query-report.service
sudo systemctl start rgbdns-query-report.service
sudo journalctl -u rgbdns-query-report.service -n 50 --no-pagerAn authentication failure normally means the API key is wrong, revoked, or
was left as a placeholder. A 550 domain error means the From: domain is
not the verified Resend domain.
msmtp can submit through Gmail directly. Gmail's SMTP submission endpoint is
smtp.gmail.com on port 587 with STARTTLS and authentication. Use a Google App
Password, not the normal account password. App Passwords require 2-Step
Verification and may be unavailable for some managed accounts. See Google's
SMTP settings and
App Password guidance.
Create an App Password for the Gmail account that will send the report, then edit:
sudo vi /etc/msmtprcdefaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
account gmail
host smtp.gmail.com
port 587
user deliverable@gmail.com
password YOUR_16_DIGIT_APP_PASSWORD
from deliverable@gmail.com
account default : gmail
Protect the file and select msmtp for the report:
sudo chmod 600 /etc/msmtprc
sudo vi /etc/rgbdns/query-report.envREPORT_SENDMAIL=/usr/bin/msmtp
REPORT_FROM=deliverable@gmail.com
Test through Gmail:
printf 'From: deliverable@gmail.com\nTo: deliverable@gmail.com\nSubject: Gmail SMTP test\n\nSMTP test\n' |
sudo /usr/bin/msmtp -t -vUse native Sendmail when the host already depends on it, other local services
submit mail through /usr/sbin/sendmail, or you need a conventional MTA queue
and daemon. It can relay through Resend, but its configuration is more involved:
SASL support, an auth database, Sendmail macros, TLS trust, daemon reloads, and
hostname identity all matter.
Installing Sendmail alone is not enough. Without a smarthost, Sendmail tries to deliver directly to the recipient's MX. Gmail commonly rejects that direct mail with:
550 5.7.26 Your email has been blocked because the sender is unauthenticated
On Debian/Ubuntu:
sudo apt-get update
sudo apt-get install sasl2-bin libsasl2-modules
sudo /usr/share/sendmail/update_authEdit the source auth file:
sudo vi /etc/mail/authinfoAdd one line with the real API key:
AuthInfo:smtp.resend.com "U:resend" "P:re_YOUR_RESEND_API_KEY" "M:PLAIN"
Protect and compile it. The sh -c is important because shell redirection
happens before sudo when written directly as sudo makemap ... < file:
sudo chmod 600 /etc/mail/authinfo
sudo sh -c 'makemap hash /etc/mail/authinfo < /etc/mail/authinfo'Verify both files exist:
sudo ls -l /etc/mail/authinfo /etc/mail/authinfo.dbEdit the macro configuration:
sudo vi /etc/mail/sendmail.mcAdd these definitions before the MAILER lines:
define(`SMART_HOST', `smtp.resend.com')dnl
define(`RELAY_MAILER_ARGS', `TCP $h 587')dnl
define(`ESMTP_MAILER_ARGS', `TCP $h 587')dnl
FEATURE(`authinfo', `hash -o /etc/mail/authinfo.db')dnl
define(`confAUTH_MECHANISMS', `PLAIN LOGIN')dnl
define(`confCACERT', `/etc/ssl/certs/ca-certificates.crt')dnlRebuild and restart Sendmail:
sudo make -C /etc/mail
sudo systemctl restart sendmailThe generated configuration must contain the smarthost:
sudo grep '^DS' /etc/mail/sendmail.cfExpected output:
DSsmtp.resend.com
If it still says DS with no host, or the delivery logs show
gmail-smtp-in.google.com, Sendmail is still delivering directly and the
smarthost configuration was not applied.
Sendmail may warn about an unqualified AWS hostname. Set the primary's local hostname and map it locally:
sudo hostnamectl set-hostname a.ns.cron.sh
sudo vi /etc/hostsEnsure this line exists:
172.31.60.189 a.ns.cron.sh a
Verify:
hostname --fqdn
getent hosts a.ns.cron.shThe hostname warning is separate from Resend authentication, but fixing it gives Sendmail a stable local identity.
Point rgbdns back to native Sendmail:
sudo vi /etc/rgbdns/query-report.envREPORT_SENDMAIL=/usr/sbin/sendmail
REPORT_FROM=rgbdns@hurz.net
printf 'From: rgbdns@hurz.net\nTo: deliverable@gmail.com\nSubject: Resend test\n\nSMTP test\n' |
sudo /usr/sbin/sendmail -tThen run the report and inspect the MTA logs:
sudo systemctl reset-failed rgbdns-query-report.service
sudo systemctl start rgbdns-query-report.service
sudo journalctl -u sendmail --since "5 minutes ago" --no-pager
sudo mailqSuccessful Sendmail delivery should include:
relay=smtp.resend.com
dsn=2.0.0
stat=Sent
The queue should be empty after successful delivery. If the log instead shows
relay=gmail-smtp-in.google.com, Sendmail is bypassing Resend. If it shows
535 Authentication credentials invalid, check the API key and auth map. If
it shows 550 ... domain is not verified, use a verified domain in both
REPORT_FROM and the Sendmail/msmtp sender configuration.
rgbdns can publish the DNS records needed for inbound mail, but rgbdns is not an SMTP receiver or forwarding service. A self-hosted setup would look like:
dick@hurz.net
│ MX
▼
mail.hurz.net
│ inbound SMTP
▼
Postfix or Sendmail
│ forwarding with SRS
▼
deliverable@gmail.com
The hurz.net zone needs an MX record pointing to a mail host and an address
record for that host. In tinydns-compatible source syntax, the records are
equivalent to:
@hurz.net::mail.hurz.net:10:3600
+mail.hurz.net:SERVER_PUBLIC_IP:3600
The mail host must have a stable public address and a matching reverse-DNS name. Publish the MX and address records only after the mail service is ready to accept connections.
A self-hosted forwarder must:
- accept inbound TCP port 25 from the Internet;
- configure
hurz.netas a local/accepted domain; - create an alias from
dicktodeliverable@gmail.com; - forward through an authenticated relay such as Resend or Gmail;
- use Sender Rewriting Scheme (SRS) when forwarding, so SPF checks survive;
- preserve useful
From:,To:,Message-ID, andReceived:headers; - provide queueing, retry, TLS, logging, spam controls, and abuse protection;
- publish appropriate SPF, DKIM, and DMARC records for the sending path.
An alias that blindly pipes incoming mail to Gmail is not sufficient. The forwarded message's original SPF identity will usually fail at Gmail, and DMARC alignment may fail without SRS or an equivalent forwarding service. AWS also commonly restricts or reputation-limits direct SMTP delivery, so the server should use the authenticated Resend/Gmail relay for outbound forwarding rather than delivering directly to Gmail MX servers.
The tested primary deployment uses a.ns.cron.sh (52.10.53.234) and the
following arrangement:
hurz.net MX 10 mail.hurz.net
mail.hurz.net A 52.10.53.234
dick@hurz.net → deliverable@gmail.com
The production changes are:
# /home/bitnami/rgbdns.data
@hurz.net::mail.hurz.net:10:3600
+mail.hurz.net:52.10.53.234:3600
# /etc/mail/local-host-names
hurz.net
mail.hurz.net
# /etc/aliases
dick: deliverable@gmail.com
After editing /etc/aliases, compile the aliases and reload the services:
sudo newaliases
sudo make -C /etc/mail
sudo systemctl restart sendmail
sudo systemctl start rgbdns-data-import.serviceThe Sendmail listener must be reachable on TCP port 25. Confirm locally with
ss -ltnp; the listener should include 0.0.0.0:25. Also allow inbound TCP
25 in the AWS security group. A local forwarding test only proves alias
expansion and outbound relay; it does not prove that an external sender can
reach the server.
Verify DNS from both the authoritative server and public resolvers:
dig @a.ns.cron.sh MX hurz.net +short
dig @1.1.1.1 MX hurz.net +short
dig @a.ns.cron.sh A mail.hurz.net +shortPublic resolvers may continue returning the previous MX until its TTL expires.
Test the alias without verbose SMTP output:
printf 'From: test@hurz.net\nTo: dick@hurz.net\nSubject: forwarding test\n\nTest.\n' |
sudo /usr/sbin/sendmail -t
sudo journalctl -u sendmail --since "5 minutes ago" -l --no-pager
sudo mailqThe successful Gmail-relay result observed in production was relay=smtp.gmail.com,
dsn=2.0.0, stat=Sent, followed by an empty Sendmail queue. The successful
TLS result was verify=OK with TLS 1.3 and an AES-256 cipher.
Self-hosting is appropriate when you already operate an MTA and want complete
control over inbound mail. For one forwarding address, Cloudflare Email
Routing is usually safer and simpler: it receives mail for hurz.net, verifies
deliverable@gmail.com as the destination, and manages the forwarding path.
It requires Cloudflare DNS and changes the domain's MX records, so it must not
be enabled alongside another inbound mail provider.
Native Sendmail can use Gmail as its authenticated smarthost. Create a Google App Password first; do not put the normal Gmail password in the auth map.
Edit the auth source:
sudo vi /etc/mail/authinfoAuthInfo:smtp.gmail.com "U:deliverable@gmail.com" "P:YOUR_16_DIGIT_APP_PASSWORD" "M:PLAIN"
Compile it:
sudo chmod 600 /etc/mail/authinfo
sudo sh -c 'makemap hash /etc/mail/authinfo < /etc/mail/authinfo'Edit the Sendmail macros:
sudo vi /etc/mail/sendmail.mcUse Gmail instead of the Resend definitions:
define(`SMART_HOST', `smtp.gmail.com')dnl
define(`RELAY_MAILER_ARGS', `TCP $h 587')dnl
define(`ESMTP_MAILER_ARGS', `TCP $h 587')dnl
FEATURE(`authinfo', `hash -o /etc/mail/authinfo.db')dnl
define(`confAUTH_MECHANISMS', `PLAIN LOGIN')dnl
define(`confCACERT', `/etc/ssl/certs/ca-certificates.crt')dnlRebuild and restart:
sudo make -C /etc/mail
sudo systemctl restart sendmailUse the Gmail account as the report sender:
sudo vi /etc/rgbdns/query-report.envREPORT_SENDMAIL=/usr/sbin/sendmail
REPORT_FROM=deliverable@gmail.com
Verify and test:
sudo grep '^DS' /etc/mail/sendmail.cf
printf 'From: deliverable@gmail.com\nTo: deliverable@gmail.com\nSubject: Gmail SMTP test\n\nSMTP test\n' |
sudo /usr/sbin/sendmail -tThe Sendmail log should show relay=smtp.gmail.com and a successful dsn=2.0.0.
Create the credential in the Google account that will submit the mail:
- Enable 2-Step Verification at https://myaccount.google.com/signinoptions/two-step-verification.
- Open https://myaccount.google.com/apppasswords.
- Create an app password named, for example,
Sendmail hurz.net. - Put the generated 16-character value in
/etc/mail/authinfo, without spaces, as theP:value.
Use the app password rather than the normal Google account password. Google may not expose App Passwords for some managed accounts, Advanced Protection accounts, or accounts configured with security keys only. Revoke the app password when this relay is retired.
Sendmail must have the system CA bundle configured for outbound TLS. These
definitions belong in /etc/mail/sendmail.mc:
define(`confCACERT_PATH', `/etc/ssl/certs')dnl
define(`confCACERT', `/etc/ssl/certs/ca-certificates.crt')dnlRefresh the CA bundle and regenerate Sendmail's configuration:
sudo update-ca-certificates
sudo make -C /etc/mail
sudo systemctl restart sendmailTest the same Gmail endpoint independently:
openssl s_client \
-connect smtp.gmail.com:587 \
-starttls smtp \
-servername smtp.gmail.com \
-CAfile /etc/ssl/certs/ca-certificates.crt \
-verify_return_error </dev/nullThe expected result is Verify return code: 0 (ok). A Sendmail delivery log
should then contain STARTTLS=client, verify=OK, and a TLS 1.2 or TLS 1.3
cipher. verify=FAIL means Sendmail did not validate the peer certificate,
even if the message was accepted; check the CA macros and rebuild.
Do not add confTLS_SRV_OPTIONS for this purpose. SRV controls Sendmail
when it accepts inbound SMTP; the report relay is an outbound client.
Commenting out SMART_HOST, the port-587 mailer arguments, and authinfo
makes Sendmail deliver directly to each recipient's MX on port 25. For Gmail,
that commonly produces:
550 5.7.26 Your email has been blocked because the sender is unauthenticated
Direct MX delivery requires a properly authenticated sending domain, stable
reverse DNS, SPF, DKIM, and suitable reputation. It is especially unreliable
for forwarding arbitrary messages because forwarding can break the original
sender's SPF and DMARC alignment. Gmail SMTP submission through
smtp.gmail.com:587 with an App Password is the practical Resend-free choice
for this host.
When changing from Resend to Gmail, comment out all Resend-specific lines in
/etc/mail/sendmail.mc; leaving RELAY_MAILER_ARGS or
ESMTP_MAILER_ARGS pointed at port 587 while removing the smarthost creates a
misleading direct-delivery configuration:
dnl define(`SMART_HOST', `smtp.resend.com')dnl
dnl define(`RELAY_MAILER_ARGS', `TCP $h 587')dnl
dnl define(`ESMTP_MAILER_ARGS', `TCP $h 587')dnl
dnl FEATURE(`authinfo', `hash -o /etc/mail/authinfo.db')dnl
dnl define(`confAUTH_MECHANISMS', `PLAIN LOGIN')dnlThen use the Gmail definitions in the previous section, run sudo make -C /etc/mail, and restart Sendmail. Confirm the active route with:
sudo grep '^DS' /etc/mail/sendmail.cf
sudo journalctl -u sendmail --since "5 minutes ago" -l --no-pagerThe route should be smtp.gmail.com, not a gmail-smtp-in MX host.
Sendmail aliases change the SMTP envelope recipient but normally preserve the
original To: header. Therefore a message sent to dick@hurz.net and
forwarded to deliverable@gmail.com may appear in Gmail as to dick, bcc: me.
That is Gmail explaining that the account received the message as the actual
envelope recipient even though the visible To: header names the original
address; it does not indicate that Sendmail added a real Bcc header.
| Situation | Recommended transport |
|---|---|
| Only rgbdns needs outbound SMTP submission | msmtp |
Existing services already use /usr/sbin/sendmail |
Native Sendmail |
| You want the smallest configuration and no MTA daemon | msmtp |
| You need a local mail queue and conventional MTA behavior | Native Sendmail |
| You want the shortest troubleshooting path | msmtp |
| You already operate Sendmail and understand its macros/SASL | Native Sendmail |
Do not configure both transports for the report at once. The active transport
is determined solely by REPORT_SENDMAIL:
sudo grep '^REPORT_SENDMAIL=' /etc/rgbdns/query-report.env
sudo readlink -f "$(sudo awk -F= '/^REPORT_SENDMAIL=/{print $2}' /etc/rgbdns/query-report.env)"/usr/bin/msmtp means the report uses msmtp. /usr/sbin/sendmail resolving
to the Sendmail executable and logs containing sm-mta mean it uses native
Sendmail.
Run these commands on the primary:
sudo systemctl status rgbdns-query-report.service --no-pager
sudo journalctl -u rgbdns-query-report.service -n 50 --no-pager
sudo journalctl -u sendmail --since "15 minutes ago" --no-pager
sudo mailqInterpret the results:
status=0/SUCCESSmeans the report transport accepted the message. It does not by itself prove Gmail delivered it.dsn=2.0.0andstat=Sentfor the Resend relay indicate successful handoff to Resend.535 Authentication credentials invalidmeans the Resend SMTP credentials are wrong or the auth map was not rebuilt.550 ... domain is not verifiedmeans the visible sender domain is not verified in Resend.550 5.7.26 ... unauthenticatedtogether with agmail-smtp-inrelay means direct MX delivery reached Gmail but failed sender authentication. Use the authenticated Gmail smarthost or configure full direct-mail authentication.- An empty queue after a permanent failure is expected; Sendmail rejected the message rather than retaining it for retry.
Check the recipient's Spam folder only after the relay log shows successful
handoff to Resend or Gmail. Avoid sendmail -v when an auth map is configured:
verbose SMTP output can expose the Base64-encoded SMTP credential. If that
happens, revoke the exposed Resend key or Gmail App Password and replace it in
/etc/mail/authinfo before testing again.