mailrs-dmarc

DMARC (RFC 7489) aggregate report tooling for Rust — result recording, XML report generation, report-mail formatting, rua extraction. Fills the gap mail-auth leaves on the receiving / aggregating side.
Extracted from mailrs so any Rust SMTP server can produce the daily aggregate reports their reporting partners expect.
What it covers
mail-auth (excellent) handles DMARC verification — pulling the policy, evaluating SPF/DKIM alignment, deciding pass/fail. This crate covers what comes after:
| Step |
mailrs-dmarc |
mail-auth |
Parse _dmarc.<domain> policy |
— |
✓ |
| Verify SPF + DKIM + alignment |
— |
✓ |
| Record per-message results |
✓ ([DmarcStore]) |
— |
| Generate aggregate XML (RFC 7489 §12.4) |
✓ ([generate_dmarc_report_xml]) |
— |
| Format report mail (multipart + gzip + base64) |
✓ ([format_report_email]) |
— |
Extract rua mailbox |
✓ ([extract_rua_from_dmarc_record]) |
— |
Quick start
use std::sync::Arc;
use mailrs_dmarc::{
DmarcResultRecord, DmarcStore, PgDmarcStore,
extract_rua_from_dmarc_record, format_report_email, generate_dmarc_report_xml,
};
# async fn run() -> Result<(), Box<dyn std::error::Error>> {
let pool = sqlx::PgPool::connect("postgres://localhost/mail").await?;
let store: Arc<PgDmarcStore> = Arc::new(PgDmarcStore::new(pool));
store.record_result(&DmarcResultRecord {
source_ip: "192.0.2.1".into(),
from_domain: "sender.example".into(),
spf_result: "pass".into(),
dkim_result: "pass".into(),
dmarc_result: "pass".into(),
disposition: "none".into(),
}).await?;
let results = store.get_results_for_date("2026-05-19").await?;
let xml = generate_dmarc_report_xml(
"Reporter Org", "postmaster@reporter.example",
"reporter.example!sender.example!2026-05-19",
"sender.example", 1715990400, 1716076800, &results,
);
let rua = extract_rua_from_dmarc_record("v=DMARC1; p=quarantine; rua=mailto:dmarc@sender.example");
let email = format_report_email(
"postmaster@reporter.example",
rua.as_deref().unwrap_or("dmarc@sender.example"),
"sender.example",
"reporter.example!sender.example!2026-05-19",
"2026-05-19", &xml,
);
# Ok(())
# }
Bring your own store
The pg-store feature gives you PgDmarcStore (the reference impl). Disable it (default-features = false) and implement [DmarcStore] yourself:
use async_trait::async_trait;
use mailrs_dmarc::{DmarcResultRecord, DmarcStore};
struct FileStore { }
#[async_trait]
impl DmarcStore for FileStore {
type Error = std::io::Error;
async fn record_result(&self, r: &DmarcResultRecord) -> Result<(), std::io::Error> {
# let _ = r; Ok(())
}
async fn get_results_for_date(&self, _date: &str) -> Result<Vec<DmarcResultRecord>, std::io::Error> {
# Ok(vec![])
}
async fn cleanup_old(&self, _days: i64) -> Result<u64, std::io::Error> {
# Ok(0)
}
}
Feature flags
| Flag |
Default |
What it enables |
pg-store |
yes |
PgDmarcStore (sqlx + Postgres reference impl) |
License
Licensed under either of Apache License 2.0 or MIT license at your option.