Victor A gives us some important information about securing E-mail with SPF, DKIM, and DMARC, which, to be honest, is "table stakes" these days if you want your E-mail to be accepted by Gmail or any of the other big providers. I implemented all these things for erbosoft.com. But there's one thing you need to close the loop on E-mail security...and it's a piece that I didn't have for awhile, and Victor doesn't mention.
When you set a report address with the rua= parameter in DMARC, you get a lot of E-mails to that address. So, the first thing is, it helps to have a separate address for those reports. I use a name that aliases to my regular mailbox, and a Sieve rule to cause all messages to that address to be filtered into their own folder.
Now you have a folder full of messages from multiple mail servers, each with their own attached report. Unfortunately, the report is in somewhat-cryptic XML and is hard to read, let alone summarize, and the problem pretty much demands an automatic solution. I toyed with the idea of writing a simple parser, and I even had Claude create some Python code for me to look in the folder and extract all the XML attachments from all those messages. Ultimately, though, I went with Robert Schumann's docker-dmarc-report. The code in that container fetches DMARC reports from an IMAP server, stores their information in MySQL, and provides a PHP-based Web application to display the reports in a human-readable fashion. I set that container up on Kosh (my home server).
The setup is easy with a docker-compose file:
services:
dmarc-report:
image: "gutmensch/dmarc-report:latest"
hostname: dmarc-report
container_name: dmarc-report
restart: always
ports:
- "8399:80"
extra_hosts:
- "database:[KOSH-IP-ADDRESS]"
environment:
- "REPORT_DB_HOST=${DB_HOST:-db}"
- "REPORT_DB_TYPE=${DB_TYPE:-mysql}"
- "REPORT_DB_PORT=${DB_PORT:-3306}"
- "REPORT_DB_NAME=${DB_NAME:-dmarc_report}"
- "REPORT_DB_USER=${DB_USER:-dmarc_report}"
- "REPORT_DB_PASS=${DB_PASSWORD}"
- "PARSER_IMAP_SERVER=${IMAP_SERVER}"
- "PARSER_IMAP_PORT=${IMAP_PORT:-993}"
- "PARSER_IMAP_USER=${IMAP_USER}"
- "PARSER_IMAP_PASS=${IMAP_PASSWORD}"
- "PARSER_IMAP_READ_FOLDER=${IMAP_READ_FOLDER:-Inbox}"
- "PARSER_IMAP_MOVE_FOLDER=${IMAP_MOVE_FOLDER:-processed}"
- "PARSER_IMAP_MOVE_FOLDER_ERR=${IMAP_MOVE_FOLDER_ERR:-error}"
- "PARSER_IMAP_SSL=${PARSER_IMAP_SSL}"
- "PARSER_IMAP_TLS=${PARSER_IMAP_TLS}"
- "PARSER_IMAP_IGNORE_ERROR=${PARSER_IMAP_IGNORE_ERROR}"
- "PARSER_XML_MAXSIZE=${PARSER_XML_MAXSIZE}"
And the corresponding .env file:
# database host address, leave empty for default host "db"
DB_HOST=database
# the database type mysql or pgsql, leave empty for default (depending on your docker-compose.yml)
DB_TYPE=
# the database port (mysql 3306) (pqsql 5432), leave empty for default (depending on your docker-compose.yml)
DB_PORT=
# the database name, leave empty for default "dmarc_report"
DB_NAME=
# the database name, leave empty for default "dmarc_report"
DB_USER=
# mysql root password. Irrelevant if you are using postgres
ROOT_DB_PASSWORD=
# database password for the database user
DB_PASSWORD=[MY-DATABASE-PASSWORD]
# the email address receiving the DMARC reports
IMAP_USER=amy
# the password for the email address receiving the DMARC reports
IMAP_PASSWORD=[MY-EMAIL-PASSWORD]
# the server the email address is hosted on
IMAP_SERVER=[MY-IMAP-SERVER]
# optional: default is 993 (or 143)
IMAP_PORT=143
# optional: default is "Inbox"
IMAP_READ_FOLDER=DMARC
# optional: default is "processed"
IMAP_MOVE_FOLDER=DMARCprocessed
# optional: default is "error"
IMAP_MOVE_FOLDER_ERR=DMARCerror
# Enable SSL and/or (START-)TLS. Set both to 0 to disable encryption (not recommended)
PARSER_IMAP_SSL=0
PARSER_IMAP_TLS=1
# Ignore ERROR: message_string() issue experienced with Exchange Online. Set to 1 to enable
PARSER_IMAP_IGNORE_ERROR=0
# Increase the maximum size of the XML file. (default is 50000 bytes)
# When the size exceeds the maximum, one could experience an error Uncaught ValueError: DOMDocument::loadXML():
# Argument #1 ($source) must not be empty.
PARSER_XML_MAXSIZE=100000
This configuration looks in my IMAP mailbox, in the folder DMARC, where my Sieve filter sends all the DMARC messages. When they're processed, they move to the DMARCprocessed folder. Any that can't be processed go to the DMARCerror folder (which is empty right now, so everything must be working OK). It checks for new messages at 15 minutes past the hour, every hour.
The MySQL server is on Kosh itself, not a container, so I had to perform some extra_hosts legerdemain in the docker-compose file to point this container at it. I mapped its Web UI to port 8399 on Kosh, where the generated reports look like this:

With this, I can examine just what's getting through and what's not.
Sadly, even with all this in place, I still get some bounces, usually from companies with third-party E-mail security systems that just don't like my poor server. But I've done what I can at the moment to ensure that, when I send a message, it goes through.