mirror of
https://github.com/domainaware/parsedmarc.git
synced 2026-08-02 13:42:19 +00:00
1543 lines
137 KiB
HTML
1543 lines
137 KiB
HTML
|
||
|
||
<!DOCTYPE html>
|
||
<html class="writer-html5" lang="en" data-content_root="./">
|
||
<head>
|
||
<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Using parsedmarc — parsedmarc 10.4.1 documentation</title>
|
||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=b86133f3" />
|
||
<link rel="stylesheet" type="text/css" href="_static/css/theme.css?v=9edc463e" />
|
||
|
||
|
||
<script src="_static/jquery.js?v=5d32c60e"></script>
|
||
<script src="_static/_sphinx_javascript_frameworks_compat.js?v=2cd50e6c"></script>
|
||
<script src="_static/documentation_options.js?v=8b0523d8"></script>
|
||
<script src="_static/doctools.js?v=fd6eb6e6"></script>
|
||
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
|
||
<script src="_static/js/theme.js"></script>
|
||
<link rel="index" title="Index" href="genindex.html" />
|
||
<link rel="search" title="Search" href="search.html" />
|
||
<link rel="next" title="Sample outputs" href="output.html" />
|
||
<link rel="prev" title="Installation" href="installation.html" />
|
||
</head>
|
||
|
||
<body class="wy-body-for-nav">
|
||
<div class="wy-grid-for-nav">
|
||
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
|
||
<div class="wy-side-scroll">
|
||
<div class="wy-side-nav-search" >
|
||
|
||
|
||
|
||
<a href="index.html" class="icon icon-home">
|
||
parsedmarc
|
||
</a>
|
||
<div role="search">
|
||
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
|
||
<input type="text" name="q" placeholder="Search docs" aria-label="Search docs" />
|
||
<input type="hidden" name="check_keywords" value="yes" />
|
||
<input type="hidden" name="area" value="default" />
|
||
</form>
|
||
</div>
|
||
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
|
||
<p class="caption" role="heading"><span class="caption-text">Contents</span></p>
|
||
<ul class="current">
|
||
<li class="toctree-l1"><a class="reference internal" href="installation.html">Installation</a></li>
|
||
<li class="toctree-l1 current"><a class="current reference internal" href="#">Using parsedmarc</a><ul>
|
||
<li class="toctree-l2"><a class="reference internal" href="#cli-help">CLI help</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#configuration-file">Configuration file</a><ul>
|
||
<li class="toctree-l3"><a class="reference internal" href="#mailbox-messages-are-only-archived-once-the-reports-are-saved">Mailbox messages are only archived once the reports are saved</a></li>
|
||
</ul>
|
||
</li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#environment-variable-configuration">Environment variable configuration</a><ul>
|
||
<li class="toctree-l3"><a class="reference internal" href="#examples">Examples</a></li>
|
||
<li class="toctree-l3"><a class="reference internal" href="#specifying-the-config-file-via-environment-variable">Specifying the config file via environment variable</a></li>
|
||
<li class="toctree-l3"><a class="reference internal" href="#running-without-a-config-file-env-only-mode">Running without a config file (env-only mode)</a></li>
|
||
<li class="toctree-l3"><a class="reference internal" href="#docker-compose-example">Docker Compose example</a></li>
|
||
<li class="toctree-l3"><a class="reference internal" href="#docker-secrets-file-suffix">Docker secrets (<code class="docutils literal notranslate"><span class="pre">_FILE</span></code> suffix)</a></li>
|
||
<li class="toctree-l3"><a class="reference internal" href="#section-name-mapping">Section name mapping</a></li>
|
||
</ul>
|
||
</li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#using-parsedmarc-as-a-library">Using parsedmarc as a library</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#performance-tuning">Performance tuning</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#multi-tenant-support">Multi-tenant support</a></li>
|
||
<li class="toctree-l2"><a class="reference internal" href="#running-parsedmarc-as-a-systemd-service">Running parsedmarc as a systemd service</a><ul>
|
||
<li class="toctree-l3"><a class="reference internal" href="#reloading-configuration-without-restarting">Reloading configuration without restarting</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li class="toctree-l1"><a class="reference internal" href="output.html">Sample outputs</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="elasticsearch.html">Elasticsearch and Kibana</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="opensearch.html">OpenSearch and Grafana</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="kibana.html">Using the Kibana dashboards</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="splunk.html">Splunk</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="davmail.html">Accessing an inbox using OWA/EWS</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="dmarc.html">Understanding DMARC</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="contributing.html">Contributing to parsedmarc</a></li>
|
||
<li class="toctree-l1"><a class="reference internal" href="api.html">API reference</a></li>
|
||
</ul>
|
||
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
|
||
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
||
<a href="index.html">parsedmarc</a>
|
||
</nav>
|
||
|
||
<div class="wy-nav-content">
|
||
<div class="rst-content">
|
||
<div role="navigation" aria-label="Page navigation">
|
||
<ul class="wy-breadcrumbs">
|
||
<li><a href="index.html" class="icon icon-home" aria-label="Home"></a></li>
|
||
<li class="breadcrumb-item active">Using parsedmarc</li>
|
||
<li class="wy-breadcrumbs-aside">
|
||
<a href="_sources/usage.md.txt" rel="nofollow"> View page source</a>
|
||
</li>
|
||
</ul>
|
||
<hr/>
|
||
</div>
|
||
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
|
||
<div itemprop="articleBody">
|
||
|
||
<section class="tex2jax_ignore mathjax_ignore" id="using-parsedmarc">
|
||
<h1>Using parsedmarc<a class="headerlink" href="#using-parsedmarc" title="Link to this heading"></a></h1>
|
||
<section id="cli-help">
|
||
<h2>CLI help<a class="headerlink" href="#cli-help" title="Link to this heading"></a></h2>
|
||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: parsedmarc [-h] [-c CONFIG_FILE] [-r] [--strip-attachment-payloads] [-o OUTPUT]
|
||
[--aggregate-json-filename AGGREGATE_JSON_FILENAME] [--failure-json-filename FAILURE_JSON_FILENAME]
|
||
[--smtp-tls-json-filename SMTP_TLS_JSON_FILENAME] [--aggregate-csv-filename AGGREGATE_CSV_FILENAME]
|
||
[--failure-csv-filename FAILURE_CSV_FILENAME] [--smtp-tls-csv-filename SMTP_TLS_CSV_FILENAME]
|
||
[-n NAMESERVERS [NAMESERVERS ...]] [-t DNS_TIMEOUT] [--dns-retries DNS_RETRIES] [--offline] [-s]
|
||
[-w] [--verbose] [--debug] [--log-file LOG_FILE] [--no-prettify-json] [-v]
|
||
[file_path ...]
|
||
|
||
Parses DMARC reports
|
||
|
||
positional arguments:
|
||
file_path one or more paths to aggregate or failure report files, emails, mbox files, or directories
|
||
containing them
|
||
|
||
options:
|
||
-h, --help show this help message and exit
|
||
-c CONFIG_FILE, --config-file CONFIG_FILE
|
||
a path to a configuration file (--silent implied)
|
||
-r, --recursive search directories given as file_path recursively, and enable '**' recursion in glob patterns
|
||
--strip-attachment-payloads
|
||
remove attachment payloads from failure report output
|
||
-o OUTPUT, --output OUTPUT
|
||
write output files to the given directory
|
||
--aggregate-json-filename AGGREGATE_JSON_FILENAME
|
||
filename for the aggregate JSON output file
|
||
--failure-json-filename FAILURE_JSON_FILENAME
|
||
filename for the failure JSON output file
|
||
--smtp-tls-json-filename SMTP_TLS_JSON_FILENAME
|
||
filename for the SMTP TLS JSON output file
|
||
--aggregate-csv-filename AGGREGATE_CSV_FILENAME
|
||
filename for the aggregate CSV output file
|
||
--failure-csv-filename FAILURE_CSV_FILENAME
|
||
filename for the failure CSV output file
|
||
--smtp-tls-csv-filename SMTP_TLS_CSV_FILENAME
|
||
filename for the SMTP TLS CSV output file
|
||
-n NAMESERVERS [NAMESERVERS ...], --nameservers NAMESERVERS [NAMESERVERS ...]
|
||
nameservers to query
|
||
-t DNS_TIMEOUT, --dns_timeout DNS_TIMEOUT
|
||
number of seconds to wait for an answer from DNS (default: 2.0)
|
||
--dns-retries DNS_RETRIES
|
||
number of times to retry DNS queries on timeout or other transient errors (default: 0)
|
||
--offline do not make online queries for geolocation or DNS
|
||
-s, --silent only print errors
|
||
-w, --warnings print warnings in addition to errors
|
||
--verbose more verbose output
|
||
--debug print debugging information
|
||
--log-file LOG_FILE output logging to a file
|
||
--no-prettify-json output JSON in a single line without indentation
|
||
-v, --version show program's version number and exit
|
||
</pre></div>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Starting in <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> 6.0.0, most CLI options were moved to a
|
||
configuration file, described below.</p>
|
||
</div>
|
||
</section>
|
||
<section id="configuration-file">
|
||
<h2>Configuration file<a class="headerlink" href="#configuration-file" title="Link to this heading"></a></h2>
|
||
<p><code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> can be configured by supplying the path to an INI file</p>
|
||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>parsedmarc<span class="w"> </span>-c<span class="w"> </span>/etc/parsedmarc.ini
|
||
</pre></div>
|
||
</div>
|
||
<p>For example</p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="c1"># This is an example comment</span>
|
||
|
||
<span class="k">[general]</span>
|
||
<span class="na">save_aggregate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">True</span>
|
||
<span class="na">save_failure</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">True</span>
|
||
|
||
<span class="k">[imap]</span>
|
||
<span class="na">host</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">imap.example.com</span>
|
||
<span class="na">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarcresports@example.com</span>
|
||
<span class="na">password</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">$uperSecure</span>
|
||
|
||
<span class="k">[mailbox]</span>
|
||
<span class="na">watch</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">True</span>
|
||
<span class="na">delete</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">False</span>
|
||
|
||
<span class="k">[elasticsearch]</span>
|
||
<span class="na">hosts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">127.0.0.1:9200</span>
|
||
<span class="na">ssl</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">False</span>
|
||
|
||
<span class="k">[opensearch]</span>
|
||
<span class="na">hosts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">https://admin:admin@127.0.0.1:9200</span>
|
||
<span class="na">ssl</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">True</span>
|
||
|
||
<span class="k">[splunk_hec]</span>
|
||
<span class="na">url</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">https://splunkhec.example.com</span>
|
||
<span class="na">token</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">HECTokenGoesHere</span>
|
||
<span class="na">index</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">email</span>
|
||
|
||
<span class="k">[s3]</span>
|
||
<span class="na">bucket</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">my-bucket</span>
|
||
<span class="na">path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">parsedmarc</span>
|
||
|
||
<span class="k">[syslog]</span>
|
||
<span class="na">server</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">localhost</span>
|
||
<span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">514</span>
|
||
|
||
<span class="k">[gelf]</span>
|
||
<span class="na">host</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">logger</span>
|
||
<span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">12201</span>
|
||
<span class="na">mode</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">tcp</span>
|
||
|
||
<span class="k">[webhook]</span>
|
||
<span class="na">aggregate_url</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">https://aggregate_url.example.com</span>
|
||
<span class="na">failure_url</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">https://failure_url.example.com</span>
|
||
<span class="na">smtp_tls_url</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">https://smtp_tls_url.example.com</span>
|
||
<span class="na">timeout</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">60</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The full set of configuration options are:</p>
|
||
<ul>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">general</span></code></p>
|
||
<ul>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">save_aggregate</span></code> - bool: Save aggregate report data to
|
||
Elasticsearch, Splunk and/or S3</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">save_failure</span></code> - bool: Save failure report data to
|
||
Elasticsearch, Splunk and/or S3</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">save_smtp_tls</span></code> - bool: Save SMTP-STS report data to
|
||
Elasticsearch, Splunk and/or S3</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">index_prefix_domain_map</span></code> - str: Path to a YAML file mapping
|
||
OpenSearch/Elasticsearch index prefixes to domain names</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">strip_attachment_payloads</span></code> - bool: Remove attachment
|
||
payloads from results</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">silent</span></code> - bool: Set this to <code class="docutils literal notranslate"><span class="pre">False</span></code> to output results to STDOUT</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">output</span></code> - str: Directory to place JSON and CSV files in. This is required if you set either of the JSON output file options.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">archive_directory</span></code> - str: Optional. When set, successfully
|
||
processed report files given as local file/directory path
|
||
arguments are moved into
|
||
<code class="docutils literal notranslate"><span class="pre"><archive_directory>/<year>/<month>/<Aggregate|Failure|SMTP-TLS>/</span></code>
|
||
(year and month come from the report’s own begin/arrival date, with
|
||
the month zero-padded). A successfully parsed report whose archive
|
||
date can’t be determined is left in place with a logged warning.
|
||
Files that fail to parse as a report are moved to
|
||
<code class="docutils literal notranslate"><span class="pre"><archive_directory>/Invalid/</span></code>; files that fail for other reasons,
|
||
such as transient I/O errors, are left in place so a later run can
|
||
retry them. An existing destination file is never overwritten; a
|
||
numeric suffix is appended before the extension (e.g.
|
||
<code class="docutils literal notranslate"><span class="pre">report-1.xml</span></code>). This applies only to direct local file input —
|
||
reports fetched from mailboxes (IMAP, Microsoft Graph, Gmail API,
|
||
Maildir) use <code class="docutils literal notranslate"><span class="pre">[mailbox]</span> <span class="pre">archive_folder</span></code> instead, and mbox files are
|
||
never moved. Files already inside <code class="docutils literal notranslate"><span class="pre">archive_directory</span></code> are excluded
|
||
from processing, so the archive may safely live inside an input
|
||
directory. A failed move is logged and does not stop the run.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">aggregate_json_filename</span></code> - str: filename for the aggregate
|
||
JSON output file</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">failure_json_filename</span></code> - str: filename for the failure
|
||
JSON output file</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">ip_db_path</span></code> - str: An optional custom path to a MMDB file
|
||
from IPinfo, MaxMind, or DBIP</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">ipinfo_url</span></code> - str: Overrides the default download URL for the
|
||
bundled IPinfo Lite MMDB (env var:
|
||
<code class="docutils literal notranslate"><span class="pre">PARSEDMARC_GENERAL_IPINFO_URL</span></code>). The pre-9.10 name <code class="docutils literal notranslate"><span class="pre">ip_db_url</span></code> is
|
||
still accepted as a deprecated alias and logs a warning.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">ipinfo_api_token</span></code> - str: Optional <a class="reference external" href="https://ipinfo.io/developers/lite-api">IPinfo Lite REST API</a> token. When
|
||
set, IP lookups hit the API first for the freshest country/ASN data
|
||
and fall back to the local MMDB on rate limit, quota exhaustion, or
|
||
network errors. An invalid token exits the process with a fatal error.
|
||
Ignored when <code class="docutils literal notranslate"><span class="pre">offline</span></code> is set. The Lite tier is free and has no
|
||
documented monthly request cap; see the IPinfo Lite docs for current
|
||
limits. (env var: <code class="docutils literal notranslate"><span class="pre">PARSEDMARC_GENERAL_IPINFO_API_TOKEN</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">offline</span></code> - bool: Do not use online queries for geolocation
|
||
or DNS. Also disables automatic downloading of the IP-to-country
|
||
database and reverse DNS map.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">always_use_local_files</span></code> - Disables the download of the
|
||
IP-to-country database and reverse DNS map</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">local_reverse_dns_map_path</span></code> - Overrides the default local file path to use for the reverse DNS map</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">reverse_dns_map_url</span></code> - Overrides the default download URL for the reverse DNS map</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">local_psl_overrides_path</span></code> - Overrides the default local file path to use for the PSL overrides list</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">psl_overrides_url</span></code> - Overrides the default download URL for the PSL overrides list</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">nameservers</span></code> - str: A comma separated list of
|
||
DNS resolvers (Default: <code class="docutils literal notranslate"><span class="pre">[Cloudflare's</span> <span class="pre">public</span> <span class="pre">resolvers]</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">dns_test_address</span></code> - str: a dummy address used for DNS pre-flight checks
|
||
(Default: 1.1.1.1)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">dns_timeout</span></code> - float: DNS timeout period</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">debug</span></code> - bool: Print debugging messages</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">silent</span></code> - bool: Only print errors (Default: <code class="docutils literal notranslate"><span class="pre">True</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">fail_on_output_error</span></code> - bool: Exit with a non-zero status code if
|
||
any configured output destination fails while saving/publishing
|
||
reports (Default: <code class="docutils literal notranslate"><span class="pre">False</span></code>)</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>This option only controls the process exit code. Retaining mailbox
|
||
messages whose reports could not be saved is automatic and happens
|
||
either way — see
|
||
<a class="reference internal" href="#mailbox-messages-are-only-archived-once-the-reports-are-saved">Mailbox messages are only archived once the reports are saved</a>.</p>
|
||
</div>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">log_file</span></code> - str: Write log messages to a file at this path</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">n_procs</span></code> - int: Number of processes to run in parallel when
|
||
parsing report files passed directly as CLI arguments, messages
|
||
in mbox files, and messages from mailbox connections (IMAP,
|
||
Microsoft Graph, Gmail API, Maildir), including watch mode
|
||
(Default: <code class="docutils literal notranslate"><span class="pre">1</span></code>)</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Setting this to a number larger than one can improve
|
||
performance when processing thousands of files or messages</p>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Only parsing is parallelized across worker processes. Fetching
|
||
messages, deduplicating reports, archiving/deleting mailbox
|
||
messages, and saving/publishing to outputs all stay sequential
|
||
in the main process. Each worker process keeps its own DNS/GeoIP
|
||
cache.</p>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">mailbox</span></code></p>
|
||
<ul>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">reports_folder</span></code> - str: The mailbox folder (or label for
|
||
Gmail) where the incoming reports can be found
|
||
(Default: <code class="docutils literal notranslate"><span class="pre">INBOX</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">archive_folder</span></code> - str: The mailbox folder (or label for
|
||
Gmail) to sort processed emails into (Default: <code class="docutils literal notranslate"><span class="pre">Archive</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">watch</span></code> - bool: Use the IMAP <code class="docutils literal notranslate"><span class="pre">IDLE</span></code> command to process
|
||
messages as they arrive or poll MS Graph for new messages</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">delete</span></code> - bool: Delete messages after processing them,
|
||
instead of archiving them</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">delete_aggregate</span></code> - bool: Delete aggregate report messages
|
||
after processing them, instead of archiving them
|
||
(Default: the value of <code class="docutils literal notranslate"><span class="pre">delete</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">delete_failure</span></code> - bool: Delete failure report messages
|
||
after processing them, instead of archiving them
|
||
(Default: the value of <code class="docutils literal notranslate"><span class="pre">delete</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">delete_smtp_tls</span></code> - bool: Delete SMTP TLS report messages
|
||
after processing them, instead of archiving them
|
||
(Default: the value of <code class="docutils literal notranslate"><span class="pre">delete</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">delete_invalid</span></code> - bool: Delete messages that could not be
|
||
parsed, instead of archiving them in the <code class="docutils literal notranslate"><span class="pre">Invalid</span></code>
|
||
subfolder, where they can be inspected for debugging
|
||
(Default: the value of <code class="docutils literal notranslate"><span class="pre">delete</span></code>)</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Each of these four options overrides <code class="docutils literal notranslate"><span class="pre">delete</span></code> for one kind of
|
||
message only, and the other three keep inheriting <code class="docutils literal notranslate"><span class="pre">delete</span></code>. So
|
||
<code class="docutils literal notranslate"><span class="pre">delete</span> <span class="pre">=</span> <span class="pre">True</span></code> combined with <code class="docutils literal notranslate"><span class="pre">delete_failure</span> <span class="pre">=</span> <span class="pre">False</span></code> archives
|
||
failure report messages while deleting processed aggregate and
|
||
SMTP TLS report messages — and unparseable ones, unless
|
||
<code class="docutils literal notranslate"><span class="pre">delete_invalid</span> <span class="pre">=</span> <span class="pre">False</span></code> is set as well.</p>
|
||
</div>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">test</span></code> - bool: Do not move or delete messages</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">batch_size</span></code> - int: Number of messages to read and process
|
||
before saving. Default <code class="docutils literal notranslate"><span class="pre">10</span></code>. Use <code class="docutils literal notranslate"><span class="pre">0</span></code> for no limit.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">check_timeout</span></code> - int: Number of seconds to wait for an IMAP
|
||
IDLE response or the number of seconds until the next
|
||
mail check (Default: <code class="docutils literal notranslate"><span class="pre">30</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">max_unsaved_retries</span></code> - int: How many times a batch of messages
|
||
whose reports could not be saved is retried before its messages
|
||
are moved to the <code class="docutils literal notranslate"><span class="pre">Unsaved</span></code> archive subfolder instead of being
|
||
retried again (Default: <code class="docutils literal notranslate"><span class="pre">2</span></code>, i.e. the initial attempt plus two
|
||
retries). Use <code class="docutils literal notranslate"><span class="pre">0</span></code> to move messages on the first failed save;
|
||
negative values are rejected.
|
||
Failures are counted in memory, so the cap applies across watch-mode
|
||
checks within one long-running process, not across separate one-shot
|
||
runs. See
|
||
<a class="reference internal" href="#mailbox-messages-are-only-archived-once-the-reports-are-saved">Mailbox messages are only archived once the reports are saved</a>
|
||
below.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">since</span></code> - str: Search for messages since certain time. (Examples: <code class="docutils literal notranslate"><span class="pre">5m|3h|2d|1w</span></code>)
|
||
Acceptable units - {“m”:“minutes”, “h”:“hours”, “d”:“days”, “w”:“weeks”}.
|
||
Defaults to <code class="docutils literal notranslate"><span class="pre">1d</span></code> if incorrect value is provided.</p></li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">imap</span></code></p>
|
||
<ul>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">host</span></code> - str: The IMAP server hostname or IP address</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">port</span></code> - int: The IMAP server port (Default: <code class="docutils literal notranslate"><span class="pre">993</span></code>)</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">%</span></code> characters must be escaped with another <code class="docutils literal notranslate"><span class="pre">%</span></code> character,
|
||
so use <code class="docutils literal notranslate"><span class="pre">%%</span></code> wherever a <code class="docutils literal notranslate"><span class="pre">%</span></code> character is used.</p>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Starting in version 8.0.0, most options from the <code class="docutils literal notranslate"><span class="pre">imap</span></code>
|
||
section have been moved to the <code class="docutils literal notranslate"><span class="pre">mailbox</span></code> section.</p>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>If your host recommends another port, still try 993</p>
|
||
</div>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">ssl</span></code> - bool: Use an encrypted SSL/TLS connection
|
||
(Default: <code class="docutils literal notranslate"><span class="pre">True</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">skip_certificate_verification</span></code> - bool: Skip certificate
|
||
verification (not recommended)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">user</span></code> - str: The IMAP user</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">password</span></code> - str: The IMAP password</p></li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">msgraph</span></code></p>
|
||
<ul>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">auth_method</span></code> - str: Authentication method, valid types are
|
||
<code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code>, <code class="docutils literal notranslate"><span class="pre">DeviceCode</span></code>, <code class="docutils literal notranslate"><span class="pre">ClientSecret</span></code>, <code class="docutils literal notranslate"><span class="pre">Certificate</span></code>, or
|
||
<code class="docutils literal notranslate"><span class="pre">ClientAssertion</span></code> (Default: <code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code>).</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">user</span></code> - str: The M365 user, required when the auth method is
|
||
UsernamePassword</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">password</span></code> - str: The user password, required when the auth
|
||
method is UsernamePassword</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">client_id</span></code> - str: The app registration’s client ID</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">client_secret</span></code> - str: The app registration’s secret</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">certificate_path</span></code> - str: Path to a PEM or PKCS12 certificate
|
||
including the private key. Required when the auth method is
|
||
<code class="docutils literal notranslate"><span class="pre">Certificate</span></code></p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">certificate_password</span></code> - str: Optional password for the
|
||
certificate file when using <code class="docutils literal notranslate"><span class="pre">Certificate</span></code> auth</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">client_assertion</span></code> - str: A signed JWT client assertion, required
|
||
when the auth method is <code class="docutils literal notranslate"><span class="pre">ClientAssertion</span></code></p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">client_assertion</span></code> is a static value, so it is only usable for as
|
||
long as the JWT it contains remains unexpired (assertions are
|
||
typically short-lived, on the order of minutes). It is best suited
|
||
to short one-shot runs; for long-running <code class="docutils literal notranslate"><span class="pre">watch</span></code> mode, prefer
|
||
<code class="docutils literal notranslate"><span class="pre">Certificate</span></code> or <code class="docutils literal notranslate"><span class="pre">ClientSecret</span></code> auth, which can refresh
|
||
indefinitely.</p>
|
||
</div>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">tenant_id</span></code> - str: The Azure AD tenant ID. This is required
|
||
for all auth methods except UsernamePassword.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">mailbox</span></code> - str: The mailbox name. This defaults to the
|
||
current user if using the UsernamePassword auth method, but
|
||
could be a shared mailbox if the user has access to the mailbox</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">graph_url</span></code> - str: Microsoft Graph URL. Allows for use of National Clouds (ex Azure Gov)
|
||
(Default: <a class="reference external" href="https://graph.microsoft.com">https://graph.microsoft.com</a>)</p>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Warning</p>
|
||
<p>Setting <code class="docutils literal notranslate"><span class="pre">graph_url</span></code> alone is <strong>not</strong> sufficient for a national/sovereign
|
||
cloud tenant. It only changes the Microsoft Graph API root; the
|
||
Microsoft Entra ID (Azure AD) token endpoint used for authentication is
|
||
not currently configurable in parsedmarc or <code class="docutils literal notranslate"><span class="pre">mailsuite</span></code>, and always
|
||
defaults to the global <code class="docutils literal notranslate"><span class="pre">https://login.microsoftonline.com</span></code>. A true
|
||
national-cloud deployment also needs its own Entra ID endpoint, so
|
||
<code class="docutils literal notranslate"><span class="pre">graph_url</span></code> by itself only helps if your tenant is registered in the
|
||
global cloud but you specifically need to reach one of these Graph API
|
||
roots (per <a class="reference external" href="https://learn.microsoft.com/en-us/graph/deployments">Microsoft’s national cloud deployment docs</a>):</p>
|
||
<table class="docutils align-default">
|
||
<thead>
|
||
<tr class="row-odd"><th class="head"><p>National cloud</p></th>
|
||
<th class="head"><p>Microsoft Graph URL</p></th>
|
||
<th class="head"><p>Entra ID endpoint (not configurable here)</p></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="row-even"><td><p>Global (default)</p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">https://graph.microsoft.com</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">https://login.microsoftonline.com</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p>US Government L4 (GCC High)</p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">https://graph.microsoft.us</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">https://login.microsoftonline.us</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-even"><td><p>US Government L5 (DoD)</p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">https://dod-graph.microsoft.us</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">https://login.microsoftonline.us</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p>China, operated by 21Vianet</p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">https://microsoftgraph.chinacloudapi.cn</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">https://login.chinacloudapi.cn</span></code></p></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">token_file</span></code> - str: Path to save the token file
|
||
(Default: <code class="docutils literal notranslate"><span class="pre">.token</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">allow_unencrypted_storage</span></code> - bool: Allows the Azure Identity
|
||
module to fall back to unencrypted token cache (Default: <code class="docutils literal notranslate"><span class="pre">False</span></code>).
|
||
Even if enabled, the cache will always try encrypted storage first.</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">token_file</span></code> stores the serialized authentication record; the
|
||
underlying MSAL persistent token cache is separately named
|
||
<code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code>, not <code class="docutils literal notranslate"><span class="pre">mailsuite</span></code>’s own default cache name. This is
|
||
deliberate: the Graph mailbox backend used to live directly in
|
||
parsedmarc, and moved into the <code class="docutils literal notranslate"><span class="pre">mailsuite</span></code> dependency in parsedmarc
|
||
9.11.0. Explicitly keeping the <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> cache name means tokens
|
||
cached before that move keep working after upgrading — there is
|
||
nothing to migrate and no action needed on your part.</p>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>You must create an app registration in Azure AD and have an
|
||
admin grant the Microsoft Graph <code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code>
|
||
(delegated) permission to the app. If you are using
|
||
<code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code> auth and the mailbox is different from the
|
||
username, you must grant the app <code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite.Shared</span></code>.</p>
|
||
</div>
|
||
<table class="docutils align-default">
|
||
<thead>
|
||
<tr class="row-odd"><th class="head"><p>Auth method</p></th>
|
||
<th class="head"><p>Reading (own mailbox)</p></th>
|
||
<th class="head"><p>Reading (shared mailbox)</p></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code> / <code class="docutils literal notranslate"><span class="pre">DeviceCode</span></code> (delegated)</p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite.Shared</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">ClientSecret</span></code> / <code class="docutils literal notranslate"><span class="pre">Certificate</span></code> / <code class="docutils literal notranslate"><span class="pre">ClientAssertion</span></code> (application)</p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code> (application), scoped via <code class="docutils literal notranslate"><span class="pre">New-ApplicationAccessPolicy</span></code></p></td>
|
||
<td><p>same as own mailbox — app-only access is scoped by policy, not by permission name</p></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="admonition tip">
|
||
<p class="admonition-title">Tip</p>
|
||
<p><strong>Troubleshooting connections.</strong> Run with <code class="docutils literal notranslate"><span class="pre">--verbose</span></code> to log a
|
||
redacted connection summary (auth method, tenant, client ID,
|
||
mailbox, Graph URL) before the connection attempt, and with
|
||
<code class="docutils literal notranslate"><span class="pre">--debug</span></code> to additionally surface the underlying library activity —
|
||
<code class="docutils literal notranslate"><span class="pre">azure.identity</span></code> token acquisition (including <code class="docutils literal notranslate"><span class="pre">AADSTS</span></code> error codes
|
||
from Entra ID, which distinguish a local configuration problem from
|
||
an Exchange Online-side one), Microsoft Graph SDK requests, and
|
||
<code class="docutils literal notranslate"><span class="pre">httpx</span></code> HTTP request lines. Secret values (passwords, client
|
||
secrets, certificate passwords) are never written to logs.
|
||
Connection, mailbox fetch, message send, and <code class="docutils literal notranslate"><span class="pre">--watch</span></code> failures
|
||
each log a single ERROR line naming the mailbox, tenant, auth
|
||
method, and the Graph <code class="docutils literal notranslate"><span class="pre">request-id</span></code>/<code class="docutils literal notranslate"><span class="pre">client-request-id</span></code> when
|
||
available — worth quoting verbatim when contacting Microsoft
|
||
support.</p>
|
||
</div>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Warning</p>
|
||
<p>If you are using the <code class="docutils literal notranslate"><span class="pre">ClientSecret</span></code> auth method, you need to
|
||
grant the <code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code> (application) permission to the
|
||
app. You must also restrict the application’s access to a
|
||
specific mailbox since it allows all mailboxes by default.
|
||
Use the <code class="docutils literal notranslate"><span class="pre">New-ApplicationAccessPolicy</span></code> command in the
|
||
Exchange PowerShell module. If you need to scope the policy to
|
||
shared mailboxes, you can add them to a mail enabled security
|
||
group and use that as the group id.</p>
|
||
<div class="highlight-powershell notranslate"><div class="highlight"><pre><span></span><span class="nb">New-ApplicationAccessPolicy</span> <span class="n">-AccessRight</span> <span class="n">RestrictAccess</span>
|
||
<span class="n">-AppId</span> <span class="s2">"<CLIENT_ID>"</span> <span class="n">-PolicyScopeGroupId</span> <span class="s2">"<MAILBOX>"</span>
|
||
<span class="n">-Description</span> <span class="s2">"Restrict access to dmarc reports mailbox."</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The same application permission and mailbox scoping guidance
|
||
applies to the <code class="docutils literal notranslate"><span class="pre">Certificate</span></code> and <code class="docutils literal notranslate"><span class="pre">ClientAssertion</span></code> auth methods.</p>
|
||
</div>
|
||
<p><strong>Sending the summary email via Microsoft Graph.</strong>
|
||
When <code class="docutils literal notranslate"><span class="pre">[msgraph]</span></code> is configured and <code class="docutils literal notranslate"><span class="pre">[smtp]</span></code> has a <code class="docutils literal notranslate"><span class="pre">to</span></code> value but no
|
||
<code class="docutils literal notranslate"><span class="pre">host</span></code>, the periodic summary email is sent through the same
|
||
already-authenticated Graph mailbox connection used for reading
|
||
(<code class="docutils literal notranslate"><span class="pre">/users/{mailbox}/sendMail</span></code>), and a copy is saved to Sent Items.
|
||
When <code class="docutils literal notranslate"><span class="pre">[smtp]</span> <span class="pre">host</span></code> is set, SMTP is used regardless of whether
|
||
<code class="docutils literal notranslate"><span class="pre">[msgraph]</span></code> is also configured — SMTP is always preferred, with no
|
||
automatic fallback to Graph on SMTP failure.</p>
|
||
<p>Example config combining <code class="docutils literal notranslate"><span class="pre">[msgraph]</span></code> with a <code class="docutils literal notranslate"><span class="pre">[smtp]</span></code> section that
|
||
only sets <code class="docutils literal notranslate"><span class="pre">to</span></code>/<code class="docutils literal notranslate"><span class="pre">subject</span></code> (no <code class="docutils literal notranslate"><span class="pre">host</span></code>):</p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[msgraph]</span>
|
||
<span class="na">auth_method</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">Certificate</span>
|
||
<span class="na">client_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">mailbox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarc-reports@example.com</span>
|
||
<span class="na">certificate_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/path/to/cert.pem</span>
|
||
|
||
<span class="k">[smtp]</span>
|
||
<span class="na">to</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">admin@example.com</span>
|
||
<span class="na">subject</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">DMARC Summary</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Required Microsoft Graph permissions, in addition to the
|
||
reading-related permissions documented above:</p>
|
||
<table class="docutils align-default">
|
||
<thead>
|
||
<tr class="row-odd"><th class="head"><p>Auth method</p></th>
|
||
<th class="head"><p>Reading</p></th>
|
||
<th class="head"><p>Sending (own mailbox)</p></th>
|
||
<th class="head"><p>Sending (shared mailbox)</p></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code> / <code class="docutils literal notranslate"><span class="pre">DeviceCode</span></code> (delegated)</p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code> (+<code class="docutils literal notranslate"><span class="pre">.Shared</span></code> for shared)</p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.Send</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.Send.Shared</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">ClientSecret</span></code> / <code class="docutils literal notranslate"><span class="pre">Certificate</span></code> / <code class="docutils literal notranslate"><span class="pre">ClientAssertion</span></code> (application)</p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code> (application)</p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.Send</span></code> (application)</p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">Mail.Send</span></code> (application), scoped via <code class="docutils literal notranslate"><span class="pre">New-ApplicationAccessPolicy</span></code></p></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Warning</p>
|
||
<p>Graph-based sending is only confirmed to work with the app-only
|
||
auth methods (<code class="docutils literal notranslate"><span class="pre">ClientSecret</span></code>, <code class="docutils literal notranslate"><span class="pre">Certificate</span></code>, <code class="docutils literal notranslate"><span class="pre">ClientAssertion</span></code>).
|
||
The delegated auth methods (<code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code>, <code class="docutils literal notranslate"><span class="pre">DeviceCode</span></code>)
|
||
currently request only the <code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code>(<code class="docutils literal notranslate"><span class="pre">.Shared</span></code>) scope when
|
||
authenticating, not <code class="docutils literal notranslate"><span class="pre">Mail.Send</span></code> — so a delegated connection’s
|
||
access token will not carry <code class="docutils literal notranslate"><span class="pre">Mail.Send</span></code> even if an administrator
|
||
has granted it, and <code class="docutils literal notranslate"><span class="pre">/sendMail</span></code> calls are expected to fail with an
|
||
access-denied error regardless of what’s granted in Azure AD. Use
|
||
an app-only auth method if you need Graph-based sending.</p>
|
||
</div>
|
||
<p><strong>Minimal example configs.</strong> Each auth method needs a different
|
||
minimum set of keys. These read-only examples omit <code class="docutils literal notranslate"><span class="pre">[smtp]</span></code>; see
|
||
above for adding Graph-based sending on top of any of them.</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code> (delegated, own mailbox):</p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[msgraph]</span>
|
||
<span class="na">auth_method</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">UsernamePassword</span>
|
||
<span class="na">client_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">client_secret</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarc-reports@example.com</span>
|
||
<span class="na">password</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">DeviceCode</span></code> (delegated, interactive sign-in on first run — <code class="docutils literal notranslate"><span class="pre">user</span></code>
|
||
is the account that signs in; <code class="docutils literal notranslate"><span class="pre">mailbox</span></code> is the shared mailbox it
|
||
reads, and only needs to differ from <code class="docutils literal notranslate"><span class="pre">user</span></code> to request
|
||
<code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite.Shared</span></code> instead of plain <code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code>):</p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[msgraph]</span>
|
||
<span class="na">auth_method</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">DeviceCode</span>
|
||
<span class="na">client_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">signing-in-user@example.com</span>
|
||
<span class="na">mailbox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarc-reports@example.com</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">ClientSecret</span></code> (app-only):</p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[msgraph]</span>
|
||
<span class="na">auth_method</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">ClientSecret</span>
|
||
<span class="na">client_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">client_secret</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">mailbox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarc-reports@example.com</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">Certificate</span></code> (app-only):</p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[msgraph]</span>
|
||
<span class="na">auth_method</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">Certificate</span>
|
||
<span class="na">client_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">certificate_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/path/to/cert.pem</span>
|
||
<span class="na">mailbox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarc-reports@example.com</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><code class="docutils literal notranslate"><span class="pre">ClientAssertion</span></code> (app-only, short-lived JWT — see the note above
|
||
about its unsuitability for <code class="docutils literal notranslate"><span class="pre">watch</span></code> mode):</p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[msgraph]</span>
|
||
<span class="na">auth_method</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">ClientAssertion</span>
|
||
<span class="na">client_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">tenant_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">client_assertion</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">...</span>
|
||
<span class="na">mailbox</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">dmarc-reports@example.com</span>
|
||
</pre></div>
|
||
</div>
|
||
<div class="admonition tip">
|
||
<p class="admonition-title">Tip</p>
|
||
<p><strong>Troubleshooting.</strong></p>
|
||
<table class="docutils align-default">
|
||
<thead>
|
||
<tr class="row-odd"><th class="head"><p>Error</p></th>
|
||
<th class="head"><p>Cause</p></th>
|
||
<th class="head"><p>Fix</p></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="row-even"><td><p><em>“…needs permission to access resources in your organization that only an admin can grant”</em> / “Admin consent required”</p></td>
|
||
<td><p>A delegated auth method (<code class="docutils literal notranslate"><span class="pre">UsernamePassword</span></code>, <code class="docutils literal notranslate"><span class="pre">DeviceCode</span></code>) is authenticating with a scope (<code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite</span></code> or <code class="docutils literal notranslate"><span class="pre">Mail.ReadWrite.Shared</span></code>) the tenant admin hasn’t consented to yet.</p></td>
|
||
<td><p>Have an Entra ID admin grant consent: Azure Portal → <strong>Enterprise Applications</strong> → <em>your app</em> → <strong>Permissions</strong> → <strong>Grant admin consent</strong>, or <code class="docutils literal notranslate"><span class="pre">az</span> <span class="pre">ad</span> <span class="pre">app</span> <span class="pre">permission</span> <span class="pre">admin-consent</span> <span class="pre">--id</span> <span class="pre"><CLIENT_ID></span></code>. This is separate from the <code class="docutils literal notranslate"><span class="pre">New-ApplicationAccessPolicy</span></code> step above, which only applies to app-only auth.</p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">ErrorItemNotFound:</span> <span class="pre">...</span> <span class="pre">Default</span> <span class="pre">folder</span> <span class="pre">Root</span> <span class="pre">not</span> <span class="pre">found</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">mailsuite</span></code> can resolve the well-known folders (<code class="docutils literal notranslate"><span class="pre">Inbox</span></code>, <code class="docutils literal notranslate"><span class="pre">Archive</span></code>, <code class="docutils literal notranslate"><span class="pre">Drafts</span></code>, <code class="docutils literal notranslate"><span class="pre">Sent</span> <span class="pre">Items</span></code>, <code class="docutils literal notranslate"><span class="pre">Deleted</span> <span class="pre">Items</span></code>, <code class="docutils literal notranslate"><span class="pre">Junk</span> <span class="pre">Email</span></code>) even when a mailbox’s folder hierarchy hasn’t fully provisioned, but a <strong>custom, non-well-known</strong> <code class="docutils literal notranslate"><span class="pre">reports_folder</span></code> name still fails to resolve on such a mailbox.</p></td>
|
||
<td><p>Point <code class="docutils literal notranslate"><span class="pre">reports_folder</span></code> at (or under) one of the six well-known folder names above, or sign into the (shared) mailbox once via Outlook/OWA to force Exchange to provision it, then retry.</p></td>
|
||
</tr>
|
||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">RuntimeError:</span> <span class="pre">Event</span> <span class="pre">loop</span> <span class="pre">is</span> <span class="pre">closed</span></code></p></td>
|
||
<td><p>Historical bug, fixed in <code class="docutils literal notranslate"><span class="pre">mailsuite</span></code> 2.0.2. Not reachable with the <code class="docutils literal notranslate"><span class="pre">mailsuite>=2.2.2</span></code> this project requires.</p></td>
|
||
<td><p>Confirm your installed <code class="docutils literal notranslate"><span class="pre">mailsuite</span></code> version is current (<code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">show</span> <span class="pre">mailsuite</span></code>); upgrade if it’s somehow pinned below 2.0.2.</p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p>Invalid/rejected timestamp in the <code class="docutils literal notranslate"><span class="pre">since</span></code>/<code class="docutils literal notranslate"><span class="pre">receivedDateTime</span></code> filter</p></td>
|
||
<td><p>Historical bug (parsedmarc <a class="reference external" href="https://github.com/domainaware/parsedmarc/pull/706">#706</a>/<a class="reference external" href="https://github.com/domainaware/parsedmarc/pull/708">#708</a>): older versions appended a spurious <code class="docutils literal notranslate"><span class="pre">Z</span></code> to an already-UTC-offset ISO timestamp. Fixed since parsedmarc 9.5.1/9.5.5.</p></td>
|
||
<td><p>Upgrade parsedmarc if you’re on a version older than 9.5.5.</p></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">elasticsearch</span></code></p>
|
||
<ul>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">hosts</span></code> - str: A comma separated list of hostnames and ports
|
||
or URLs (e.g. <code class="docutils literal notranslate"><span class="pre">127.0.0.1:9200</span></code> or
|
||
<code class="docutils literal notranslate"><span class="pre">https://user:secret@localhost</span></code>)</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Special characters in the username or password must be
|
||
<a class="reference external" href="https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters">URL encoded</a>.</p>
|
||
</div>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">user</span></code> - str: Basic auth username</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">password</span></code> - str: Basic auth password</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">api_key</span></code> - str: API key</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">ssl</span></code> - bool: Use an encrypted SSL/TLS connection
|
||
(Default: <code class="docutils literal notranslate"><span class="pre">True</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">timeout</span></code> - float: Timeout in seconds (Default: 60)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">cert_path</span></code> - str: Path to a trusted certificates</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">skip_certificate_verification</span></code> - bool: Skip certificate
|
||
verification (not recommended)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">index_suffix</span></code> - str: A suffix to apply to the index names</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">index_prefix</span></code> - str: A prefix to apply to the index names</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">monthly_indexes</span></code> - bool: Use monthly indexes instead of daily indexes</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">number_of_shards</span></code> - int: The number of shards to use when
|
||
creating the index (Default: <code class="docutils literal notranslate"><span class="pre">1</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">number_of_replicas</span></code> - int: The number of replicas to use when
|
||
creating the index (Default: <code class="docutils literal notranslate"><span class="pre">0</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">serverless</span></code> - bool: Set to <code class="docutils literal notranslate"><span class="pre">True</span></code> when targeting an Elastic Cloud
|
||
Serverless project. Serverless manages sharding and replication itself
|
||
and rejects the <code class="docutils literal notranslate"><span class="pre">number_of_shards</span></code> / <code class="docutils literal notranslate"><span class="pre">number_of_replicas</span></code> index settings
|
||
with HTTP 400. With this flag set, parsedmarc strips those keys from the
|
||
settings sent at index creation; any other settings (e.g.
|
||
<code class="docutils literal notranslate"><span class="pre">refresh_interval</span></code>) are passed through unchanged (Default: <code class="docutils literal notranslate"><span class="pre">False</span></code>)</p></li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">opensearch</span></code></p>
|
||
<ul>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">hosts</span></code> - str: A comma separated list of hostnames and ports
|
||
or URLs (e.g. <code class="docutils literal notranslate"><span class="pre">127.0.0.1:9200</span></code> or
|
||
<code class="docutils literal notranslate"><span class="pre">https://user:secret@localhost</span></code>)</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Special characters in the username or password must be
|
||
<a class="reference external" href="https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters">URL encoded</a>.</p>
|
||
</div>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">user</span></code> - str: Basic auth username</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">password</span></code> - str: Basic auth password</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">api_key</span></code> - str: API key</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">auth_type</span></code> - str: Authentication type: <code class="docutils literal notranslate"><span class="pre">basic</span></code> (default) or <code class="docutils literal notranslate"><span class="pre">awssigv4</span></code> (the key <code class="docutils literal notranslate"><span class="pre">authentication_type</span></code> is accepted as an alias for this option)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">aws_region</span></code> - str: AWS region for SigV4 authentication
|
||
(required when <code class="docutils literal notranslate"><span class="pre">auth_type</span> <span class="pre">=</span> <span class="pre">awssigv4</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">aws_service</span></code> - str: AWS service for SigV4 signing (Default: <code class="docutils literal notranslate"><span class="pre">es</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">ssl</span></code> - bool: Use an encrypted SSL/TLS connection
|
||
(Default: <code class="docutils literal notranslate"><span class="pre">True</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">timeout</span></code> - float: Timeout in seconds (Default: 60)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">cert_path</span></code> - str: Path to a trusted certificates</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">skip_certificate_verification</span></code> - bool: Skip certificate
|
||
verification (not recommended)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">index_suffix</span></code> - str: A suffix to apply to the index names</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">index_prefix</span></code> - str: A prefix to apply to the index names</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">monthly_indexes</span></code> - bool: Use monthly indexes instead of daily indexes</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">number_of_shards</span></code> - int: The number of shards to use when
|
||
creating the index (Default: <code class="docutils literal notranslate"><span class="pre">1</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">number_of_replicas</span></code> - int: The number of replicas to use when
|
||
creating the index (Default: <code class="docutils literal notranslate"><span class="pre">0</span></code>)</p></li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">splunk_hec</span></code></p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">url</span></code> - str: The URL of the Splunk HTTP Events Collector (HEC)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">token</span></code> - str: The HEC token</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">index</span></code> - str: The Splunk index to use</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">skip_certificate_verification</span></code> - bool: Skip certificate
|
||
verification (not recommended)</p></li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">kafka</span></code></p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">hosts</span></code> - str: A comma separated list of Kafka hosts</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">user</span></code> - str: The Kafka user</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">passsword</span></code> - str: The Kafka password</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">ssl</span></code> - bool: Use an encrypted SSL/TLS connection (Default: <code class="docutils literal notranslate"><span class="pre">True</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">skip_certificate_verification</span></code> - bool: Skip certificate
|
||
verification (not recommended)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">aggregate_topic</span></code> - str: The Kafka topic for aggregate reports</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">failure_topic</span></code> - str: The Kafka topic for failure reports</p></li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">smtp</span></code></p>
|
||
<p>The results email is only sent when at least one aggregate, failure,
|
||
or SMTP TLS report was parsed during the run; an empty run (e.g. an
|
||
empty inbox) skips the email instead of sending headers-only CSVs.</p>
|
||
<ul>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">host</span></code> - str: The SMTP hostname. Required unless <code class="docutils literal notranslate"><span class="pre">[msgraph]</span></code> is
|
||
configured, in which case omitting it sends the summary via
|
||
Microsoft Graph instead — see “Sending the summary email via
|
||
Microsoft Graph” above.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">port</span></code> - int: The SMTP port (Default: <code class="docutils literal notranslate"><span class="pre">25</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">ssl</span></code> - bool: Require SSL/TLS instead of using STARTTLS</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">skip_certificate_verification</span></code> - bool: Skip certificate
|
||
verification (not recommended)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">user</span></code> - str: the SMTP username. SMTP-only; not used when sending
|
||
via Microsoft Graph.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">password</span></code> - str: the SMTP password. SMTP-only; not used when
|
||
sending via Microsoft Graph.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">from</span></code> - str: The From header to use in the email. SMTP-only.
|
||
When sent via Microsoft Graph, the message’s <code class="docutils literal notranslate"><span class="pre">From</span></code> is always the
|
||
<code class="docutils literal notranslate"><span class="pre">[msgraph]</span></code> mailbox — <code class="docutils literal notranslate"><span class="pre">[smtp]</span> <span class="pre">from</span></code> has no effect.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">to</span></code> - list: A list of email addresses to send to</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">subject</span></code> - str: The Subject header to use in the email
|
||
(Default: <code class="docutils literal notranslate"><span class="pre">parsedmarc</span> <span class="pre">report</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">attachment</span></code> - str: The ZIP attachment filenames
|
||
(Default: <code class="docutils literal notranslate"><span class="pre">DMARC-<YYYY-MM-DD>.zip</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">message</span></code> - str: The email message
|
||
(Default: <code class="docutils literal notranslate"><span class="pre">Please</span> <span class="pre">see</span> <span class="pre">the</span> <span class="pre">attached</span> <span class="pre">DMARC</span> <span class="pre">results.</span></code>)</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">%</span></code> characters must be escaped with another <code class="docutils literal notranslate"><span class="pre">%</span></code> character,
|
||
so use <code class="docutils literal notranslate"><span class="pre">%%</span></code> wherever a <code class="docutils literal notranslate"><span class="pre">%</span></code> character is used.</p>
|
||
</div>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">postgresql</span></code></p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">host</span></code> - str: The PostgreSQL server hostname or IP address.
|
||
Required unless <code class="docutils literal notranslate"><span class="pre">connection_string</span></code> is provided.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">port</span></code> - int: The PostgreSQL server port (Default: <code class="docutils literal notranslate"><span class="pre">5432</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">user</span></code> - str: The database user name (Optional)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">password</span></code> - str: The database user password (Optional)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">database</span></code> - str: The database name (Optional)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">connection_string</span></code> - str: A full libpq connection string or URI
|
||
(e.g. <code class="docutils literal notranslate"><span class="pre">postgresql://user:pass@host/dbname</span></code>). When provided,
|
||
all individual parameters above are ignored.</p></li>
|
||
</ul>
|
||
<p>The PostgreSQL backend is an optional extra. Install it with
|
||
<code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">parsedmarc[postgresql]</span></code> (it pulls in <code class="docutils literal notranslate"><span class="pre">psycopg</span></code>); the
|
||
prebuilt binary wheels are not available for every platform, which is
|
||
why it is not a mandatory dependency. The prebuilt Docker image
|
||
(<code class="docutils literal notranslate"><span class="pre">ghcr.io/domainaware/parsedmarc</span></code>) already bundles this extra, so the
|
||
PostgreSQL backend works out of the box in the container — <code class="docutils literal notranslate"><span class="pre">psycopg</span></code>
|
||
ships <code class="docutils literal notranslate"><span class="pre">amd64</span></code> and <code class="docutils literal notranslate"><span class="pre">arm64</span></code> binary wheels, both of which the image
|
||
supports.</p>
|
||
<p>Tables are created automatically on first run using
|
||
<code class="docutils literal notranslate"><span class="pre">CREATE</span> <span class="pre">TABLE</span> <span class="pre">IF</span> <span class="pre">NOT</span> <span class="pre">EXISTS</span></code>, so no manual schema migration is needed
|
||
for fresh installations.</p>
|
||
<p><strong>Example configuration:</strong></p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[postgresql]</span>
|
||
<span class="na">host</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">localhost</span>
|
||
<span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">5432</span>
|
||
<span class="na">user</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">parsedmarc</span>
|
||
<span class="na">password</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">secret</span>
|
||
<span class="na">database</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">parsedmarc</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Or using a DSN/URI:</p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[postgresql]</span>
|
||
<span class="na">connection_string</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">postgresql://parsedmarc:secret@localhost/parsedmarc</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Saving parsed data to PostgreSQL is controlled by the <code class="docutils literal notranslate"><span class="pre">[general]</span></code>
|
||
options <code class="docutils literal notranslate"><span class="pre">save_aggregate</span></code>, <code class="docutils literal notranslate"><span class="pre">save_failure</span></code>, and <code class="docutils literal notranslate"><span class="pre">save_smtp_tls</span></code>
|
||
(<code class="docutils literal notranslate"><span class="pre">save_forensic</span></code> is still accepted as a deprecated alias for
|
||
<code class="docutils literal notranslate"><span class="pre">save_failure</span></code>). These flags must be set to <code class="docutils literal notranslate"><span class="pre">True</span></code> for the
|
||
corresponding report types (aggregate DMARC, failure DMARC, and
|
||
SMTP TLS reports) or no data will be written to PostgreSQL, even if
|
||
this section is configured.</p>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">s3</span></code></p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">bucket</span></code> - str: The S3 bucket name</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">path</span></code> - str: The path to upload reports to (Default: <code class="docutils literal notranslate"><span class="pre">/</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">region_name</span></code> - str: The region name (Optional)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">endpoint_url</span></code> - str: The endpoint URL (Optional)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">access_key_id</span></code> - str: The access key id (Optional)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">secret_access_key</span></code> - str: The secret access key (Optional)</p></li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">syslog</span></code></p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">server</span></code> - str: The Syslog server name or IP address</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">port</span></code> - int: The port to use (Default: <code class="docutils literal notranslate"><span class="pre">514</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">protocol</span></code> - str: The protocol to use: <code class="docutils literal notranslate"><span class="pre">udp</span></code>, <code class="docutils literal notranslate"><span class="pre">tcp</span></code>, or <code class="docutils literal notranslate"><span class="pre">tls</span></code> (Default: <code class="docutils literal notranslate"><span class="pre">udp</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">cafile_path</span></code> - str: Path to CA certificate file for TLS server verification (Optional)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">certfile_path</span></code> - str: Path to client certificate file for TLS authentication (Optional)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">keyfile_path</span></code> - str: Path to client private key file for TLS authentication (Optional)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">timeout</span></code> - float: Connection timeout in seconds for TCP/TLS (Default: <code class="docutils literal notranslate"><span class="pre">5.0</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">retry_attempts</span></code> - int: Number of retry attempts for failed connections (Default: <code class="docutils literal notranslate"><span class="pre">3</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">retry_delay</span></code> - int: Delay in seconds between retry attempts (Default: <code class="docutils literal notranslate"><span class="pre">5</span></code>)</p></li>
|
||
</ul>
|
||
<p><strong>Example UDP configuration (default):</strong></p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[syslog]</span>
|
||
<span class="na">server</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">syslog.example.com</span>
|
||
<span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">514</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><strong>Example TCP configuration:</strong></p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[syslog]</span>
|
||
<span class="na">server</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">syslog.example.com</span>
|
||
<span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">6514</span>
|
||
<span class="na">protocol</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">tcp</span>
|
||
<span class="na">timeout</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">10.0</span>
|
||
<span class="na">retry_attempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">5</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><strong>Example TLS configuration with server verification:</strong></p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[syslog]</span>
|
||
<span class="na">server</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">syslog.example.com</span>
|
||
<span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">6514</span>
|
||
<span class="na">protocol</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">tls</span>
|
||
<span class="na">cafile_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/path/to/ca-cert.pem</span>
|
||
<span class="na">timeout</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">10.0</span>
|
||
</pre></div>
|
||
</div>
|
||
<p><strong>Example TLS configuration with mutual authentication:</strong></p>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[syslog]</span>
|
||
<span class="na">server</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">syslog.example.com</span>
|
||
<span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">6514</span>
|
||
<span class="na">protocol</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">tls</span>
|
||
<span class="na">cafile_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/path/to/ca-cert.pem</span>
|
||
<span class="na">certfile_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/path/to/client-cert.pem</span>
|
||
<span class="na">keyfile_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/path/to/client-key.pem</span>
|
||
<span class="na">timeout</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">10.0</span>
|
||
<span class="na">retry_attempts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">3</span>
|
||
<span class="na">retry_delay</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">5</span>
|
||
</pre></div>
|
||
</div>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">gmail_api</span></code></p>
|
||
<ul>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">credentials_file</span></code> - str: Path to file containing the
|
||
credentials, None to disable (Default: <code class="docutils literal notranslate"><span class="pre">None</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">token_file</span></code> - str: Path to save the token file
|
||
(Default: <code class="docutils literal notranslate"><span class="pre">.token</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">auth_mode</span></code> - str: Authentication mode, <code class="docutils literal notranslate"><span class="pre">installed_app</span></code> (default)
|
||
or <code class="docutils literal notranslate"><span class="pre">service_account</span></code></p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">service_account_user</span></code> - str: Delegated mailbox user for Gmail
|
||
service account auth (required for domain-wide delegation). Also
|
||
accepted as <code class="docutils literal notranslate"><span class="pre">delegated_user</span></code> for backward compatibility.</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>credentials_file and token_file can be got with <a class="reference external" href="https://developers.google.com/gmail/api/quickstart/python">quickstart</a>.Please change the scope to <code class="docutils literal notranslate"><span class="pre">https://www.googleapis.com/auth/gmail.modify</span></code>.</p>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>When <code class="docutils literal notranslate"><span class="pre">auth_mode</span> <span class="pre">=</span> <span class="pre">service_account</span></code>, <code class="docutils literal notranslate"><span class="pre">credentials_file</span></code> must point to a
|
||
Google service account key JSON file, and <code class="docutils literal notranslate"><span class="pre">token_file</span></code> is not used.</p>
|
||
</div>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">include_spam_trash</span></code> - bool: Include messages in Spam and
|
||
Trash when searching reports (Default: <code class="docutils literal notranslate"><span class="pre">False</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">scopes</span></code> - str: Comma separated list of scopes to use when
|
||
acquiring credentials
|
||
(Default: <code class="docutils literal notranslate"><span class="pre">https://www.googleapis.com/auth/gmail.modify</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">oauth2_port</span></code> - int: The TCP port for the local server to
|
||
listen on for the OAuth2 response (Default: <code class="docutils literal notranslate"><span class="pre">8080</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">paginate_messages</span></code> - bool: When <code class="docutils literal notranslate"><span class="pre">True</span></code>, fetch all applicable Gmail messages.
|
||
When <code class="docutils literal notranslate"><span class="pre">False</span></code>, only fetch up to 100 new messages per run (Default: <code class="docutils literal notranslate"><span class="pre">True</span></code>)</p></li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">log_analytics</span></code></p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">client_id</span></code> - str: The app registration’s client ID</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">client_secret</span></code> - str: The app registration’s client secret</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">tenant_id</span></code> - str: The tenant id where the app registration resides</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">dce</span></code> - str: The Data Collection Endpoint (DCE). Example: <code class="docutils literal notranslate"><span class="pre">https://{DCE-NAME}.{REGION}.ingest.monitor.azure.com</span></code>.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">dcr_immutable_id</span></code> - str: The immutable ID of the Data Collection Rule (DCR)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">dcr_aggregate_stream</span></code> - str: The stream name for aggregate reports in the DCR</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">dcr_failure_stream</span></code> - str: The stream name for the failure reports in the DCR</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">dcr_smtp_tls_stream</span></code> - str: The stream name for the SMTP TLS reports in the DCR</p></li>
|
||
</ul>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Information regarding the setup of the Data Collection Rule can be found <a class="reference external" href="https://learn.microsoft.com/en-us/azure/azure-monitor/logs/tutorial-logs-ingestion-portal">in the Azure documentation</a>.</p>
|
||
</div>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">gelf</span></code></p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">host</span></code> - str: The GELF server name or IP address</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">port</span></code> - int: The port to use</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">mode</span></code> - str: The GELF transport type to use. Valid modes: <code class="docutils literal notranslate"><span class="pre">tcp</span></code>, <code class="docutils literal notranslate"><span class="pre">udp</span></code>, <code class="docutils literal notranslate"><span class="pre">tls</span></code></p></li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">maildir</span></code></p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">maildir_path</span></code> - str: Full path for mailbox maildir location (Default: <code class="docutils literal notranslate"><span class="pre">INBOX</span></code>)</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">maildir_create</span></code> - bool: Create maildir if not present (Default: False)</p></li>
|
||
</ul>
|
||
</li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">webhook</span></code> - Post the individual reports to a webhook url with the report as the JSON body</p>
|
||
<ul class="simple">
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">aggregate_url</span></code> - str: URL of the webhook which should receive the aggregate reports</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">failure_url</span></code> - str: URL of the webhook which should receive the failure reports</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">smtp_tls_url</span></code> - str: URL of the webhook which should receive the smtp_tls reports</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">timeout</span></code> - int: Interval in which the webhook call should timeout</p></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Warning</p>
|
||
<p>It is <strong>strongly recommended</strong> to <strong>not</strong> use the <code class="docutils literal notranslate"><span class="pre">nameservers</span></code>
|
||
setting. By default, <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> uses
|
||
<a class="reference external" href="https://1.1.1.1/">Cloudflare’s public resolvers</a>, which are much faster and more
|
||
reliable than Google, Cisco OpenDNS, or even most local resolvers.</p>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">nameservers</span></code> option should only be used if your network
|
||
blocks DNS requests to outside resolvers.</p>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">save_aggregate</span></code> and <code class="docutils literal notranslate"><span class="pre">save_failure</span></code> are separate options
|
||
because you may not want to save failure reports
|
||
(formerly known as forensic reports) to your Elasticsearch instance,
|
||
particularly if you are in a highly-regulated industry that
|
||
handles sensitive data, such as healthcare or finance. If your
|
||
legitimate outgoing email fails DMARC, it is possible
|
||
that email may appear later in a failure report.</p>
|
||
<p>Failure reports contain the original headers of an email that
|
||
failed a DMARC check, and sometimes may also include the
|
||
full message body, depending on the policy of the reporting
|
||
organization.</p>
|
||
<p>Most reporting organizations do not send failure reports of any
|
||
kind for privacy reasons. While aggregate DMARC reports are sent
|
||
at least daily, it is normal to receive very few failure reports.</p>
|
||
<p>An alternative approach is to still collect failure/ruf
|
||
reports in your DMARC inbox, but run <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> with
|
||
<code class="docutils literal notranslate"><span class="pre">save_failure</span> <span class="pre">=</span> <span class="pre">True</span></code> manually on a separate IMAP folder (using
|
||
the <code class="docutils literal notranslate"><span class="pre">reports_folder</span></code> option), after you have manually moved
|
||
known samples you want to save to that folder
|
||
(e.g. malicious samples and non-sensitive legitimate samples).</p>
|
||
</div>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Warning</p>
|
||
<p>Elasticsearch 8 change limits policy for shards, restricting by
|
||
default to 1000. parsedmarc use a shard per analyzed day. If you
|
||
have more than ~3 years of data, you will need to update this
|
||
limit.
|
||
Check current usage (from Management -> Dev Tools -> Console):</p>
|
||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>GET /_cluster/health?pretty
|
||
{
|
||
...
|
||
"active_primary_shards": 932,
|
||
"active_shards": 932,
|
||
...
|
||
}
|
||
</pre></div>
|
||
</div>
|
||
<p>Update the limit to 2k per example:</p>
|
||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>PUT _cluster/settings
|
||
{
|
||
"persistent" : {
|
||
"cluster.max_shards_per_node" : 2000
|
||
}
|
||
}
|
||
</pre></div>
|
||
</div>
|
||
<p>Increasing this value increases resource usage.</p>
|
||
</div>
|
||
<section id="mailbox-messages-are-only-archived-once-the-reports-are-saved">
|
||
<h3>Mailbox messages are only archived once the reports are saved<a class="headerlink" href="#mailbox-messages-are-only-archived-once-the-reports-are-saved" title="Link to this heading"></a></h3>
|
||
<p>parsedmarc processes a mailbox in batches of <code class="docutils literal notranslate"><span class="pre">batch_size</span></code> messages. Each
|
||
batch is written to every configured output destination <em>before</em> any of
|
||
that batch’s messages are archived or deleted. If any destination reports a
|
||
failure — an Elasticsearch outage, an expired Splunk HEC token, an
|
||
unreachable Kafka broker, a full <code class="docutils literal notranslate"><span class="pre">--output</span></code> disk — the whole batch is left
|
||
in the reports folder and retried on the next run or watch-mode check, so a
|
||
report is never removed from the mailbox while it exists nowhere else
|
||
(<a class="reference external" href="https://github.com/domainaware/parsedmarc/issues/242">issue #242</a>).</p>
|
||
<p>This is all-or-nothing per batch: archiving a batch because most
|
||
destinations accepted it would still permanently lose the data for the one
|
||
that didn’t. It also applies regardless of <code class="docutils literal notranslate"><span class="pre">fail_on_output_error</span></code>, which
|
||
only controls the process exit code. Messages that could not be parsed at
|
||
all carry no report data, so they are filed under <code class="docutils literal notranslate"><span class="pre">Invalid</span></code> (or deleted per
|
||
<code class="docutils literal notranslate"><span class="pre">delete_invalid</span></code>) as usual.</p>
|
||
<p>A destination that is broken rather than briefly unavailable would
|
||
otherwise be retried forever, so retries are capped. Once a message’s batch
|
||
has failed <code class="docutils literal notranslate"><span class="pre">max_unsaved_retries</span> <span class="pre">+</span> <span class="pre">1</span></code> times (three times by default), that
|
||
message is moved to <code class="docutils literal notranslate"><span class="pre"><archive_folder>/Unsaved</span></code> and stops being retried. <strong>A
|
||
message is never deleted on this path, whatever the <code class="docutils literal notranslate"><span class="pre">delete</span></code> options say.</strong>
|
||
To recover after fixing the output destination, either move the messages
|
||
from <code class="docutils literal notranslate"><span class="pre">Archive/Unsaved</span></code> back into the reports folder, or run parsedmarc once
|
||
with <code class="docutils literal notranslate"><span class="pre">reports_folder</span> <span class="pre">=</span> <span class="pre">Archive/Unsaved</span></code>.</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>The failure counts live in memory, so they are counted per parsedmarc
|
||
process. In watch mode — a long-running process that checks the mailbox
|
||
repeatedly — the cap works as described across checks. A one-shot run
|
||
(<code class="docutils literal notranslate"><span class="pre">cron</span></code>, <code class="docutils literal notranslate"><span class="pre">systemd</span></code> timers) attempts each message exactly once and then
|
||
exits, so its counts start over every time and the default cap is never
|
||
reached: messages simply keep being retried on every run, which is the
|
||
safe direction. Set <code class="docutils literal notranslate"><span class="pre">max_unsaved_retries</span> <span class="pre">=</span> <span class="pre">0</span></code> if you want one-shot runs to
|
||
move unsavable messages to <code class="docutils literal notranslate"><span class="pre">Unsaved</span></code> immediately instead.</p>
|
||
</div>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Warning</p>
|
||
<p>Retrying a batch means re-sending it. Output destinations that
|
||
deduplicate — Elasticsearch, OpenSearch, and PostgreSQL, which recognize
|
||
an already-saved report — are unaffected, and S3 is idempotent because
|
||
each report is written to an object key built from its type, date, and
|
||
report ID, so a retry overwrites the same object. Kafka,
|
||
Splunk HEC, syslog, GELF, webhooks, Azure Log Analytics, and the
|
||
<code class="docutils literal notranslate"><span class="pre">--output</span></code> JSON/CSV files all append unconditionally, so each retry adds
|
||
another copy of every report in the batch. That is why the default retry
|
||
cap is deliberately low: at most three deliveries per report before its
|
||
message is set aside in <code class="docutils literal notranslate"><span class="pre">Unsaved</span></code>. The summary email covers everything
|
||
parsed in a run, including reports whose batch failed to save, so a report
|
||
retried across runs can also appear in more than one summary email.</p>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>Not every destination can report a failed delivery. The webhook output
|
||
deliberately logs and swallows its own HTTP and network errors, and the
|
||
syslog and GELF outputs send through Python logging handlers, which
|
||
swallow delivery errors by design — so an unreachable webhook, syslog, or
|
||
GELF endpoint is <em>not</em> treated as a failed save and does not hold a
|
||
batch’s messages back. Failures in Elasticsearch, OpenSearch, Splunk HEC,
|
||
Kafka, S3, PostgreSQL, Azure Log Analytics, and the <code class="docutils literal notranslate"><span class="pre">--output</span></code> files are
|
||
all detected and do.</p>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p><code class="docutils literal notranslate"><span class="pre">since</span></code> interacts with retries: a message that ages out of the configured
|
||
<code class="docutils literal notranslate"><span class="pre">since</span></code> window stops being fetched, and therefore stops being retried
|
||
automatically. It is never deleted or moved — it simply stays in the
|
||
reports folder until it is processed by a run with a wider (or no)
|
||
<code class="docutils literal notranslate"><span class="pre">since</span></code> window.</p>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
<section id="environment-variable-configuration">
|
||
<h2>Environment variable configuration<a class="headerlink" href="#environment-variable-configuration" title="Link to this heading"></a></h2>
|
||
<p>Any configuration option can be set via environment variables using the
|
||
naming convention <code class="docutils literal notranslate"><span class="pre">PARSEDMARC_{SECTION}_{KEY}</span></code> (uppercase). This is
|
||
especially useful for Docker deployments where file permissions make it
|
||
difficult to use config files for secrets.</p>
|
||
<p><strong>Priority order:</strong> CLI arguments > environment variables > config file > defaults</p>
|
||
<section id="examples">
|
||
<h3>Examples<a class="headerlink" href="#examples" title="Link to this heading"></a></h3>
|
||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span><span class="c1"># Set IMAP credentials via env vars</span>
|
||
<span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_IMAP_HOST</span><span class="o">=</span>imap.example.com
|
||
<span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_IMAP_USER</span><span class="o">=</span>dmarc@example.com
|
||
<span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_IMAP_PASSWORD</span><span class="o">=</span>secret
|
||
|
||
<span class="c1"># Elasticsearch</span>
|
||
<span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_ELASTICSEARCH_HOSTS</span><span class="o">=</span>http://localhost:9200
|
||
<span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_ELASTICSEARCH_SSL</span><span class="o">=</span><span class="nb">false</span>
|
||
|
||
<span class="c1"># Splunk HEC (note: section name splunk_hec becomes SPLUNK_HEC)</span>
|
||
<span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_SPLUNK_HEC_URL</span><span class="o">=</span>https://splunk.example.com
|
||
<span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_SPLUNK_HEC_TOKEN</span><span class="o">=</span>my-hec-token
|
||
<span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_SPLUNK_HEC_INDEX</span><span class="o">=</span>email
|
||
|
||
<span class="c1"># General settings</span>
|
||
<span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_GENERAL_SAVE_AGGREGATE</span><span class="o">=</span><span class="nb">true</span>
|
||
<span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_GENERAL_DEBUG</span><span class="o">=</span><span class="nb">true</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="specifying-the-config-file-via-environment-variable">
|
||
<h3>Specifying the config file via environment variable<a class="headerlink" href="#specifying-the-config-file-via-environment-variable" title="Link to this heading"></a></h3>
|
||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span><span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_CONFIG_FILE</span><span class="o">=</span>/etc/parsedmarc.ini
|
||
parsedmarc
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="running-without-a-config-file-env-only-mode">
|
||
<h3>Running without a config file (env-only mode)<a class="headerlink" href="#running-without-a-config-file-env-only-mode" title="Link to this heading"></a></h3>
|
||
<p>When no config file is given (neither <code class="docutils literal notranslate"><span class="pre">-c</span></code> flag nor <code class="docutils literal notranslate"><span class="pre">PARSEDMARC_CONFIG_FILE</span></code>),
|
||
parsedmarc will still pick up any <code class="docutils literal notranslate"><span class="pre">PARSEDMARC_*</span></code> environment variables. This
|
||
enables fully file-less deployments:</p>
|
||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span><span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_GENERAL_SAVE_AGGREGATE</span><span class="o">=</span><span class="nb">true</span>
|
||
<span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_GENERAL_OFFLINE</span><span class="o">=</span><span class="nb">true</span>
|
||
<span class="nb">export</span><span class="w"> </span><span class="nv">PARSEDMARC_ELASTICSEARCH_HOSTS</span><span class="o">=</span>http://elasticsearch:9200
|
||
parsedmarc<span class="w"> </span>/path/to/reports/*
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="docker-compose-example">
|
||
<h3>Docker Compose example<a class="headerlink" href="#docker-compose-example" title="Link to this heading"></a></h3>
|
||
<div class="highlight-yaml notranslate"><div class="highlight"><pre><span></span><span class="nt">services</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="nt">parsedmarc</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">parsedmarc:latest</span>
|
||
<span class="w"> </span><span class="nt">environment</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="nt">PARSEDMARC_IMAP_HOST</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">imap.example.com</span>
|
||
<span class="w"> </span><span class="nt">PARSEDMARC_IMAP_USER</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">dmarc@example.com</span>
|
||
<span class="w"> </span><span class="nt">PARSEDMARC_IMAP_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">${IMAP_PASSWORD}</span>
|
||
<span class="w"> </span><span class="nt">PARSEDMARC_MAILBOX_WATCH</span><span class="p">:</span><span class="w"> </span><span class="s">"true"</span>
|
||
<span class="w"> </span><span class="nt">PARSEDMARC_ELASTICSEARCH_HOSTS</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">http://elasticsearch:9200</span>
|
||
<span class="w"> </span><span class="nt">PARSEDMARC_GENERAL_SAVE_AGGREGATE</span><span class="p">:</span><span class="w"> </span><span class="s">"true"</span>
|
||
<span class="w"> </span><span class="nt">PARSEDMARC_GENERAL_SAVE_FAILURE</span><span class="p">:</span><span class="w"> </span><span class="s">"true"</span>
|
||
</pre></div>
|
||
</div>
|
||
</section>
|
||
<section id="docker-secrets-file-suffix">
|
||
<h3>Docker secrets (<code class="docutils literal notranslate"><span class="pre">_FILE</span></code> suffix)<a class="headerlink" href="#docker-secrets-file-suffix" title="Link to this heading"></a></h3>
|
||
<p>Any <code class="docutils literal notranslate"><span class="pre">PARSEDMARC_{SECTION}_{KEY}</span></code> environment variable can also be supplied
|
||
via a file by appending <code class="docutils literal notranslate"><span class="pre">_FILE</span></code> to its name. The file’s contents (with any
|
||
trailing CR/LF characters stripped) are used as the value. This is the
|
||
same convention used by the official Postgres, MariaDB, and Redis container
|
||
images, and is designed to plug straight into Docker / Docker Compose /
|
||
Kubernetes secrets so credentials never appear in plain <code class="docutils literal notranslate"><span class="pre">environment:</span></code>
|
||
blocks (where they would be readable via <code class="docutils literal notranslate"><span class="pre">docker</span> <span class="pre">inspect</span></code>, container logs,
|
||
and <code class="docutils literal notranslate"><span class="pre">/proc/<pid>/environ</span></code>).</p>
|
||
<p>The bare <code class="docutils literal notranslate"><span class="pre">DEBUG</span></code> / <code class="docutils literal notranslate"><span class="pre">PARSEDMARC_DEBUG</span></code> aliases and <code class="docutils literal notranslate"><span class="pre">PARSEDMARC_CONFIG_FILE</span></code>
|
||
do not have a <code class="docutils literal notranslate"><span class="pre">_FILE</span></code> form; only <code class="docutils literal notranslate"><span class="pre">PARSEDMARC_{SECTION}_{KEY}</span></code> vars resolved
|
||
to a known config section are eligible.</p>
|
||
<p>If both the direct env var and the <code class="docutils literal notranslate"><span class="pre">_FILE</span></code> variant are set, the <code class="docutils literal notranslate"><span class="pre">_FILE</span></code>
|
||
variant wins. If the file does not exist or is unreadable, parsedmarc
|
||
exits with a configuration error rather than silently falling back to an
|
||
empty value.</p>
|
||
<div class="highlight-yaml notranslate"><div class="highlight"><pre><span></span><span class="nt">secrets</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="nt">imap_password</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="nt">file</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">./secrets/imap_password.txt</span>
|
||
|
||
<span class="nt">services</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="nt">parsedmarc</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">parsedmarc:latest</span>
|
||
<span class="w"> </span><span class="nt">secrets</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">imap_password</span>
|
||
<span class="w"> </span><span class="nt">environment</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="nt">PARSEDMARC_IMAP_HOST</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">imap.example.com</span>
|
||
<span class="w"> </span><span class="nt">PARSEDMARC_IMAP_USER</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">dmarc@example.com</span>
|
||
<span class="w"> </span><span class="nt">PARSEDMARC_IMAP_PASSWORD_FILE</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/run/secrets/imap_password</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Note that a small set of config keys whose own names already end in
|
||
<code class="docutils literal notranslate"><span class="pre">_file</span></code> (<code class="docutils literal notranslate"><span class="pre">[general]</span> <span class="pre">log_file</span></code>, <code class="docutils literal notranslate"><span class="pre">[msgraph]</span> <span class="pre">token_file</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">[gmail_api]</span> <span class="pre">credentials_file</span></code>, <code class="docutils literal notranslate"><span class="pre">[gmail_api]</span> <span class="pre">token_file</span></code>) keep their
|
||
pre-existing meaning when set via <code class="docutils literal notranslate"><span class="pre">PARSEDMARC_..._FILE</span></code> — that env var is
|
||
the path itself, not a wrapper around a file containing the path. To pass
|
||
<em>those</em> paths via a Docker secret, double up the suffix
|
||
(<code class="docutils literal notranslate"><span class="pre">PARSEDMARC_GMAIL_API_CREDENTIALS_FILE_FILE</span></code>); the inner contents are
|
||
then read and stored as the <code class="docutils literal notranslate"><span class="pre">credentials_file</span></code> value.</p>
|
||
</section>
|
||
<section id="section-name-mapping">
|
||
<h3>Section name mapping<a class="headerlink" href="#section-name-mapping" title="Link to this heading"></a></h3>
|
||
<p>For sections with underscores in the name, the full section name is used:</p>
|
||
<table class="docutils align-default">
|
||
<thead>
|
||
<tr class="row-odd"><th class="head"><p>Section</p></th>
|
||
<th class="head"><p>Env var prefix</p></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">general</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_GENERAL_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">mailbox</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_MAILBOX_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">imap</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_IMAP_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">msgraph</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_MSGRAPH_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">elasticsearch</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_ELASTICSEARCH_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">opensearch</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_OPENSEARCH_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">splunk_hec</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_SPLUNK_HEC_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">kafka</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_KAFKA_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">smtp</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_SMTP_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">s3</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_S3_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">syslog</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_SYSLOG_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">gmail_api</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_GMAIL_API_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">maildir</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_MAILDIR_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">log_analytics</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_LOG_ANALYTICS_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-even"><td><p><code class="docutils literal notranslate"><span class="pre">gelf</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_GELF_</span></code></p></td>
|
||
</tr>
|
||
<tr class="row-odd"><td><p><code class="docutils literal notranslate"><span class="pre">webhook</span></code></p></td>
|
||
<td><p><code class="docutils literal notranslate"><span class="pre">PARSEDMARC_WEBHOOK_</span></code></p></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</section>
|
||
</section>
|
||
<section id="using-parsedmarc-as-a-library">
|
||
<h2>Using parsedmarc as a library<a class="headerlink" href="#using-parsedmarc-as-a-library" title="Link to this heading"></a></h2>
|
||
<p><code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> is also importable as a regular Python package, not just a CLI
|
||
tool. The main entry points — <code class="docutils literal notranslate"><span class="pre">parse_report_file()</span></code>, <code class="docutils literal notranslate"><span class="pre">parse_aggregate_report_xml()</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">parse_aggregate_report_file()</span></code>, <code class="docutils literal notranslate"><span class="pre">parse_failure_report()</span></code>, <code class="docutils literal notranslate"><span class="pre">parse_report_email()</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">get_dmarc_reports_from_mbox()</span></code>, <code class="docutils literal notranslate"><span class="pre">get_dmarc_reports_from_mailbox()</span></code>, and
|
||
<code class="docutils literal notranslate"><span class="pre">watch_inbox()</span></code> — are all importable
|
||
directly from the <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> package. See the <a class="reference internal" href="api.html"><span class="std std-doc">API reference</span></a> for
|
||
the full set of modules and members.</p>
|
||
<p>Each of these functions accepts either individual option keyword arguments
|
||
(<code class="docutils literal notranslate"><span class="pre">offline</span></code>, <code class="docutils literal notranslate"><span class="pre">nameservers</span></code>, <code class="docutils literal notranslate"><span class="pre">dns_timeout</span></code>, etc.) or a single <code class="docutils literal notranslate"><span class="pre">config=</span></code> keyword
|
||
argument carrying a <code class="docutils literal notranslate"><span class="pre">ParserConfig</span></code> instance:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">parsedmarc</span><span class="w"> </span><span class="kn">import</span> <span class="n">ParserConfig</span><span class="p">,</span> <span class="n">parse_report_file</span><span class="p">,</span> <span class="n">get_dmarc_reports_from_mailbox</span>
|
||
<span class="kn">from</span><span class="w"> </span><span class="nn">parsedmarc.mail</span><span class="w"> </span><span class="kn">import</span> <span class="n">IMAPConnection</span>
|
||
|
||
<span class="n">config</span> <span class="o">=</span> <span class="n">ParserConfig</span><span class="p">(</span>
|
||
<span class="n">offline</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
|
||
<span class="n">nameservers</span><span class="o">=</span><span class="p">[</span><span class="s2">"1.1.1.1"</span><span class="p">,</span> <span class="s2">"1.0.0.1"</span><span class="p">],</span>
|
||
<span class="n">dns_timeout</span><span class="o">=</span><span class="mf">5.0</span><span class="p">,</span>
|
||
<span class="p">)</span>
|
||
|
||
<span class="n">report</span> <span class="o">=</span> <span class="n">parse_report_file</span><span class="p">(</span><span class="s2">"aggregate_report.xml.gz"</span><span class="p">,</span> <span class="n">config</span><span class="o">=</span><span class="n">config</span><span class="p">)</span>
|
||
|
||
<span class="n">connection</span> <span class="o">=</span> <span class="n">IMAPConnection</span><span class="p">(</span>
|
||
<span class="n">host</span><span class="o">=</span><span class="s2">"imap.example.com"</span><span class="p">,</span> <span class="n">user</span><span class="o">=</span><span class="s2">"dmarc@example.com"</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s2">"..."</span>
|
||
<span class="p">)</span>
|
||
<span class="n">results</span> <span class="o">=</span> <span class="n">get_dmarc_reports_from_mailbox</span><span class="p">(</span><span class="n">connection</span><span class="p">,</span> <span class="n">config</span><span class="o">=</span><span class="n">config</span><span class="p">)</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>A few things to keep in mind:</p>
|
||
<ul class="simple">
|
||
<li><p>When <code class="docutils literal notranslate"><span class="pre">config=</span></code> is passed, the individual option keyword arguments are
|
||
ignored in favor of the values carried on the <code class="docutils literal notranslate"><span class="pre">ParserConfig</span></code> instance.</p></li>
|
||
<li><p>Each explicitly constructed <code class="docutils literal notranslate"><span class="pre">ParserConfig</span></code> owns its own isolated caches
|
||
(IP address info, seen aggregate report IDs, and the reverse DNS map).
|
||
Omitting <code class="docutils literal notranslate"><span class="pre">config=</span></code> falls back to the process-wide caches shared by every
|
||
call that doesn’t pass one.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">keep_alive</span></code> and <code class="docutils literal notranslate"><span class="pre">n_procs</span></code> are not part of <code class="docutils literal notranslate"><span class="pre">ParserConfig</span></code> — they control
|
||
process/worker orchestration rather than parsing or enrichment behavior,
|
||
so they are always passed as separate keyword arguments.</p></li>
|
||
</ul>
|
||
</section>
|
||
<section id="performance-tuning">
|
||
<h2>Performance tuning<a class="headerlink" href="#performance-tuning" title="Link to this heading"></a></h2>
|
||
<p>For large mailbox imports or backfills, parsedmarc can consume a noticeable amount
|
||
of memory, especially when it runs on the same host as Elasticsearch or
|
||
OpenSearch. The following settings can reduce peak memory usage and make long
|
||
imports more predictable:</p>
|
||
<ul class="simple">
|
||
<li><p>Reduce <code class="docutils literal notranslate"><span class="pre">mailbox.batch_size</span></code> to smaller values such as <code class="docutils literal notranslate"><span class="pre">100-500</span></code> instead of
|
||
processing a very large message set at once. Smaller batches trade throughput
|
||
for lower peak memory use and less sink pressure.</p></li>
|
||
<li><p><code class="docutils literal notranslate"><span class="pre">n_procs</span></code> now parallelizes parsing for mailbox and mbox runs too, not just
|
||
report files passed directly as CLI arguments. It pays off most when a run
|
||
is bound by DNS/GeoIP enrichment rather than fetching or output. The
|
||
trade-off is memory and DNS load: at most roughly <code class="docutils literal notranslate"><span class="pre">2</span> <span class="pre">*</span> <span class="pre">n_procs</span></code> messages
|
||
are held in flight at once, each worker process keeps its own DNS/GeoIP
|
||
cache, and DNS query volume can multiply by up to <code class="docutils literal notranslate"><span class="pre">n_procs</span></code>.</p></li>
|
||
<li><p>Use <code class="docutils literal notranslate"><span class="pre">mailbox.since</span></code> to process reports in smaller time windows such as <code class="docutils literal notranslate"><span class="pre">1d</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">7d</span></code>, or another interval that fits the backlog. This makes it easier to catch
|
||
up incrementally instead of loading an entire mailbox history in one run.</p></li>
|
||
<li><p>Set <code class="docutils literal notranslate"><span class="pre">strip_attachment_payloads</span> <span class="pre">=</span> <span class="pre">True</span></code> when failure reports contain large
|
||
attachments and you do not need to retain the raw payloads in the parsed
|
||
output.</p></li>
|
||
<li><p>Prefer running parsedmarc separately from Elasticsearch or OpenSearch, or
|
||
reserve enough RAM for both services if they must share a host.</p></li>
|
||
<li><p>For very large imports, prefer incremental supervised runs, such as a
|
||
scheduler or systemd service, over infrequent massive backfills.</p></li>
|
||
</ul>
|
||
<p>These are operational tuning recommendations rather than hard requirements, but
|
||
they are often enough to avoid memory pressure and reduce failures during
|
||
high-volume mailbox processing.</p>
|
||
</section>
|
||
<section id="multi-tenant-support">
|
||
<h2>Multi-tenant support<a class="headerlink" href="#multi-tenant-support" title="Link to this heading"></a></h2>
|
||
<p>Starting in <code class="docutils literal notranslate"><span class="pre">8.19.0</span></code>, ParseDMARC provides multi-tenant support by placing data into separate OpenSearch or Elasticsearch index prefixes. To set this up, create a YAML file that is formatted where each key is a tenant name, and the value is a list of domains related to that tenant, not including subdomains, like this:</p>
|
||
<div class="highlight-yaml notranslate"><div class="highlight"><pre><span></span><span class="nt">example</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">example.com</span>
|
||
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">example.net</span>
|
||
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">example.org</span>
|
||
|
||
<span class="nt">whalensolutions</span><span class="p">:</span>
|
||
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">whalensolutions.com</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Save it to disk where the user running ParseDMARC can read it, then set <code class="docutils literal notranslate"><span class="pre">index_prefix_domain_map</span></code> to that filepath in the <code class="docutils literal notranslate"><span class="pre">[general]</span></code> section of the ParseDMARC configuration file and do not set an <code class="docutils literal notranslate"><span class="pre">index_prefix</span></code> option in the <code class="docutils literal notranslate"><span class="pre">[elasticsearch]</span></code> or <code class="docutils literal notranslate"><span class="pre">[opensearch]</span></code> sections.</p>
|
||
<p>When configured correctly, if ParseDMARC finds that a report is related to a domain in the mapping, the report will be saved in an index name that has the tenant name prefixed to it with a trailing underscore. Then, you can use the security features of OpenSearch or the ELK stack to only grant users access to the indexes that they need.</p>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>A domain cannot be used in multiple tenant lists. Only the first prefix list that contains the matching domain is used.</p>
|
||
</div>
|
||
<p>Each key must be a tenant name and each value a <em>list</em> of domain names, all strings; a file of any other shape is rejected at startup.</p>
|
||
<p>The index migrations and backfills that run at startup cover every tenant prefix in the map, in addition to the unprefixed indexes that hold reports for domains the map does not list. A configuration reload (<code class="docutils literal notranslate"><span class="pre">SIGHUP</span></code>) re-reads the map, so a newly onboarded tenant is covered without restarting parsedmarc. See <a class="reference internal" href="elasticsearch.html#backfilling-the-combined-dkim-spf-result-fields"><span class="std std-ref">Backfilling the combined DKIM/SPF result fields</span></a> for the details.</p>
|
||
</section>
|
||
<section id="running-parsedmarc-as-a-systemd-service">
|
||
<h2>Running parsedmarc as a systemd service<a class="headerlink" href="#running-parsedmarc-as-a-systemd-service" title="Link to this heading"></a></h2>
|
||
<p>Use systemd to run <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> as a service and process reports as
|
||
they arrive.</p>
|
||
<p>This assumes <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> has been installed into
|
||
<code class="docutils literal notranslate"><span class="pre">/opt/parsedmarc/venv</span></code> under a <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> system user, as described
|
||
in <a class="reference internal" href="installation.html#installing-parsedmarc"><span class="std std-ref">Installing parsedmarc</span></a>.</p>
|
||
<p>Protect the <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> configuration file from prying eyes</p>
|
||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>sudo<span class="w"> </span>chown<span class="w"> </span>root:parsedmarc<span class="w"> </span>/etc/parsedmarc.ini
|
||
sudo<span class="w"> </span>chmod<span class="w"> </span><span class="nv">u</span><span class="o">=</span>rw,g<span class="o">=</span>r,o<span class="o">=</span><span class="w"> </span>/etc/parsedmarc.ini
|
||
</pre></div>
|
||
</div>
|
||
<p>Create the service configuration file</p>
|
||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>sudo<span class="w"> </span>nano<span class="w"> </span>/etc/systemd/system/parsedmarc.service
|
||
</pre></div>
|
||
</div>
|
||
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[Unit]</span>
|
||
<span class="na">Description</span><span class="o">=</span><span class="s">parsedmarc mailbox watcher</span>
|
||
<span class="na">Documentation</span><span class="o">=</span><span class="s">https://domainaware.github.io/parsedmarc/</span>
|
||
<span class="na">Wants</span><span class="o">=</span><span class="s">network-online.target</span>
|
||
<span class="na">After</span><span class="o">=</span><span class="s">network.target network-online.target elasticsearch.service</span>
|
||
|
||
<span class="k">[Service]</span>
|
||
<span class="na">ExecStart</span><span class="o">=</span><span class="s">/opt/parsedmarc/venv/bin/parsedmarc -c /etc/parsedmarc.ini</span>
|
||
<span class="na">ExecReload</span><span class="o">=</span><span class="s">/bin/kill -HUP $MAINPID</span>
|
||
<span class="na">KillSignal</span><span class="o">=</span><span class="s">SIGTERM</span>
|
||
<span class="na">TimeoutStopSec</span><span class="o">=</span><span class="s">60</span>
|
||
<span class="na">User</span><span class="o">=</span><span class="s">parsedmarc</span>
|
||
<span class="na">Group</span><span class="o">=</span><span class="s">parsedmarc</span>
|
||
<span class="na">Restart</span><span class="o">=</span><span class="s">always</span>
|
||
<span class="na">RestartSec</span><span class="o">=</span><span class="s">5m</span>
|
||
|
||
<span class="k">[Install]</span>
|
||
<span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>Then, enable the service</p>
|
||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>sudo<span class="w"> </span>systemctl<span class="w"> </span>daemon-reload
|
||
sudo<span class="w"> </span>systemctl<span class="w"> </span><span class="nb">enable</span><span class="w"> </span>parsedmarc.service
|
||
sudo<span class="w"> </span>service<span class="w"> </span>parsedmarc<span class="w"> </span>restart
|
||
</pre></div>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>You must also run the above commands whenever you edit
|
||
<code class="docutils literal notranslate"><span class="pre">parsedmarc.service</span></code>.</p>
|
||
</div>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Warning</p>
|
||
<p>Always restart the service every time you upgrade to a new version of
|
||
<code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code>:</p>
|
||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>sudo<span class="w"> </span>service<span class="w"> </span>parsedmarc<span class="w"> </span>restart
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>On <code class="docutils literal notranslate"><span class="pre">systemctl</span> <span class="pre">stop</span></code>/<code class="docutils literal notranslate"><span class="pre">restart</span></code> (or Ctrl-C) <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> finishes the
|
||
current batch, flushes its outputs, and exits cleanly. Shutdown is
|
||
observed at batch boundaries, so the worst-case delay is roughly
|
||
<code class="docutils literal notranslate"><span class="pre">mailbox_check_timeout</span></code> (default 30s) plus the batch’s processing and
|
||
flush time. Keep <code class="docutils literal notranslate"><span class="pre">TimeoutStopSec</span></code> comfortably above
|
||
<code class="docutils literal notranslate"><span class="pre">mailbox_check_timeout</span></code> (≈2×, and raise both together) or systemd will
|
||
<code class="docutils literal notranslate"><span class="pre">SIGKILL</span></code> mid-batch. In the foreground, a second Ctrl-C force-quits
|
||
immediately, skipping the output flush.</p>
|
||
</div>
|
||
<section id="reloading-configuration-without-restarting">
|
||
<h3>Reloading configuration without restarting<a class="headerlink" href="#reloading-configuration-without-restarting" title="Link to this heading"></a></h3>
|
||
<p>When running in watch mode, <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> supports reloading its
|
||
configuration file without restarting the service or interrupting
|
||
report processing that is already in progress. Send a <code class="docutils literal notranslate"><span class="pre">SIGHUP</span></code> signal
|
||
to the process, or use <code class="docutils literal notranslate"><span class="pre">systemctl</span> <span class="pre">reload</span></code> if the unit file includes
|
||
the <code class="docutils literal notranslate"><span class="pre">ExecReload</span></code> line shown above:</p>
|
||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>sudo<span class="w"> </span>systemctl<span class="w"> </span>reload<span class="w"> </span>parsedmarc
|
||
</pre></div>
|
||
</div>
|
||
<p>The reload takes effect after the current batch of reports finishes
|
||
processing and all output operations (Elasticsearch, Kafka, S3, etc.)
|
||
for that batch have completed. The following settings are reloaded:</p>
|
||
<ul class="simple">
|
||
<li><p>All output destinations (Elasticsearch, OpenSearch, Kafka, S3,
|
||
Splunk, syslog, GELF, webhooks, Log Analytics)</p></li>
|
||
<li><p>Multi-tenant index prefix domain map (<code class="docutils literal notranslate"><span class="pre">index_prefix_domain_map</span></code> —
|
||
the referenced YAML file is re-read on reload)</p></li>
|
||
<li><p>DNS and GeoIP settings (<code class="docutils literal notranslate"><span class="pre">nameservers</span></code>, <code class="docutils literal notranslate"><span class="pre">dns_timeout</span></code>, <code class="docutils literal notranslate"><span class="pre">ip_db_path</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">ip_db_url</span></code>, <code class="docutils literal notranslate"><span class="pre">offline</span></code>, etc.)</p></li>
|
||
<li><p>Processing flags (<code class="docutils literal notranslate"><span class="pre">strip_attachment_payloads</span></code>, <code class="docutils literal notranslate"><span class="pre">batch_size</span></code>,
|
||
<code class="docutils literal notranslate"><span class="pre">check_timeout</span></code>, etc.)</p></li>
|
||
<li><p>Log level (<code class="docutils literal notranslate"><span class="pre">debug</span></code>, <code class="docutils literal notranslate"><span class="pre">verbose</span></code>, <code class="docutils literal notranslate"><span class="pre">warnings</span></code>, <code class="docutils literal notranslate"><span class="pre">silent</span></code>)</p></li>
|
||
</ul>
|
||
<p>Mailbox connection settings (IMAP host/credentials, Microsoft Graph,
|
||
Gmail API, Maildir path) are <strong>not</strong> reloaded — changing those still
|
||
requires a full restart.</p>
|
||
<p>On a <strong>successful</strong> reload, existing output client connections are
|
||
closed and new ones are created from the updated configuration. The
|
||
service then resumes watching with the new settings.</p>
|
||
<p>If the new configuration file contains errors (missing required
|
||
settings, unreachable output destinations, etc.), the <strong>entire reload
|
||
is aborted</strong> — no output clients are replaced and the previous
|
||
configuration remains fully active. This means a typo in one section
|
||
will not take down an otherwise working setup. Check the logs for
|
||
details:</p>
|
||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>journalctl<span class="w"> </span>-u<span class="w"> </span>parsedmarc.service<span class="w"> </span>-r
|
||
</pre></div>
|
||
</div>
|
||
<p>To check the status of the service, run:</p>
|
||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>service<span class="w"> </span>parsedmarc<span class="w"> </span>status
|
||
</pre></div>
|
||
</div>
|
||
<div class="admonition note">
|
||
<p class="admonition-title">Note</p>
|
||
<p>In the event of a crash, systemd will restart the service after 10
|
||
minutes, but the <code class="docutils literal notranslate"><span class="pre">service</span> <span class="pre">parsedmarc</span> <span class="pre">status</span></code> command will only show
|
||
the logs for the current process. To view the logs for previous runs
|
||
as well as the current process (newest to oldest), run:</p>
|
||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>journalctl<span class="w"> </span>-u<span class="w"> </span>parsedmarc.service<span class="w"> </span>-r
|
||
</pre></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
</section>
|
||
|
||
|
||
</div>
|
||
</div>
|
||
<footer><div class="rst-footer-buttons" role="navigation" aria-label="Footer">
|
||
<a href="installation.html" class="btn btn-neutral float-left" title="Installation" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left" aria-hidden="true"></span> Previous</a>
|
||
<a href="output.html" class="btn btn-neutral float-right" title="Sample outputs" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right" aria-hidden="true"></span></a>
|
||
</div>
|
||
|
||
<hr/>
|
||
|
||
<div role="contentinfo">
|
||
<p>© Copyright 2018 - 2025, Sean Whalen and contributors.</p>
|
||
</div>
|
||
|
||
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
|
||
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
|
||
provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
||
|
||
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
<script>
|
||
jQuery(function () {
|
||
SphinxRtdTheme.Navigation.enable(true);
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html> |