What you will build
The other guides go deep on one subject. This one stays at the top: is everything fine, and if not, what to do first. The API lets you:
- post a weekly digest to a team channel: global score, the few actions that matter, what changed;
- build a status tile in your own dashboard or BI, next to your other metrics;
- track recommendations like tickets, and close them once the fix is in.
Before you start
- An API key created from the dashboard, under Account, Developer, on a plan that includes API access.
- Something to supervise: verified domains or IPs. The more sources are connected (DMARC, TLS reports, ESP, Postmaster), the more complete the picture.
export SA_KEY="sa_live_your_key"
export SA_API="https://senderaudit.com/api/v1"
import os, requests
API = "https://senderaudit.com/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SA_KEY']}"
Step 1: the state of the account in one call
GET /overview returns what the dashboard home shows, for every domain and IP of the account, or for one resource group with ?group=.
curl -s "$SA_API/overview" -H "Authorization: Bearer $SA_KEY" | jq '{global, kpis}'
ov = session.get(f"{API}/overview").json()
g, k = ov["global"], ov["kpis"]
print(f'Score {g["score"]}/100 ({g["verdict"]}), {g["attention_count"]} of {g["assets_count"]} assets with actions to take')
The verdict
global sums up the account:
| Field | What it is |
|---|---|
score | Average of the health scores of every domain and IP, out of 100. Capped at 50 while one of them is in crisis |
verdict | good, attention (warnings to deal with) or critical (a failure, or a critical blocklisting) |
crisis | The asset on a critical blocklist, if any: type, id, name |
trend_30d | Score change over 30 days |
assets_count, attention_count | Domains and IPs, and how many of them have at least one action to take |
The KPIs
kpis holds four rates over the last 30 days, each with its value over the 30 days before (_prev) to show a trend. All are between 0 and 1, null when there is no data.
| Field | What it is | Comes from |
|---|---|---|
dmarc_pass_rate | Share of the DMARC volume that is legitimate, identified relays included | DMARC reports |
esp_bounce_rate | Bounces over sent | Connected ESP |
esp_spam_rate | Complaints over delivered | Connected ESP |
critical_actions | Number of actions with a failure status | Health cards |
What to do, and on what
actions: every fix across all domains and IPs, sorted bygain, the points it would bring back. Each one names its asset (asset_type,asset_id,asset_name), with alabel, astatus(failorwarn) and asummary. The first five make a good to-do list.assets: one line per domain and IP with itsscore,status(healthy,attention,critical,crisis), counts of failures and warnings, and asparkline.activity: the latest notable events (blocklisting, DMARC policy change, new sending source…), the same as domain events.sources: which data sources are connected (domains,ips,dmarc,tlsrpt,esp,gpt), and aprecisionofpartial,goodorcomplete. Apartialprecision means the score rests on few signals: say so in your report.
A weekly digest
lines = [f'*Deliverability: {g["score"]}/100, {g["verdict"]}*']
if g["crisis"]:
lines.append(f'Blocklisted: {g["crisis"]["name"]}')
if k["dmarc_pass_rate"] is not None:
lines.append(f'DMARC compliance {k["dmarc_pass_rate"]:.1%} (was {k["dmarc_pass_rate_prev"] or 0:.1%})')
for a in ov["actions"][:5]:
lines.append(f'• {a["asset_name"]}: {a["label"]} (+{a["gain"]})')
message = "\n".join(lines) # post it to your chat tool's incoming webhook
Step 2: track the recommendations
Health cards say what is wrong right now. Recommendations go further: every night, Sender Audit evaluates rules on the history of each domain (a DMARC failure spike, a policy left at none for too long, a legitimate sender that started failing, a complaint rate above Gmail’s limit, an SPF record over 10 lookups…) and on correlations between signals.
GET /recommendations lists them, most severe first. Filters: domain, group, severity and status (active by default, resolved, dismissed, all).
curl -s "$SA_API/recommendations?severity=critical" -H "Authorization: Bearer $SA_KEY"
for rec in session.get(f"{API}/recommendations").json():
print(rec["severity"], rec["domain"], rec["rule_id"], rec["evidence_json"])
| Field | What it is |
|---|---|
rule_id | The rule that fired, stable over time: auth_dmarc_fail_spike, auth_spf_invalid, auth_spf_lookup_limit, auth_legit_sender_failing, rep_rbl_critical, eng_spam_rate_high, eng_bounce_spike… |
severity | critical, warning, info or positive |
category | authentication, reputation or engagement |
evidence_json | The figures behind it, for example the spam rate or the score before and after |
action_url | Where to fix it in the dashboard, relative to https://senderaudit.com |
detected_at, expires_at | When it was last confirmed, and when it lapses if it is not confirmed again |
rule_id plus domain identifies a recommendation: it is the key to use to open one ticket per recommendation in your own tracker, and to avoid duplicates.
A recommendation resolves itself when its condition disappears. You can also close it:
POST /recommendations/{id}/resolveonce you have applied the fix. If the condition is still there the next night, it comes back.POST /recommendations/{id}/dismisswhen it does not apply to you. It is not suggested again.
Step 3: TLS reporting
TLS reports (TLS-RPT, RFC 8460) are the inbound counterpart of DMARC: servers that send to you report whether they managed to deliver over an encrypted connection, and why not when they failed. They matter as soon as you publish MTA-STS or DANE.
GET /tlsrpt/address returns your collection address and the ready-made record, txt_record, to publish at _smtp._tls.example.com:
v=TLSRPTv1; rua=mailto:your-address@...
GET /domains/{id}/setup proposes the same record, completed with what is already published.
Then:
GET /tlsrpt/summary:success_rateas a percentage (0 to 100), totals of successful and failed sessions, and the five most frequent failure types (top_failures). Filtersdomainandgroup.GET /tlsrpt/stats: successes and failures per day, overdays.GET /tlsrpt/reportslists the reports,order=failuresputs those with failures first;GET /tlsrpt/reports/{id}details each failure: type (certificate-expired,sts-policy-fetch-error…), sending and receiving servers, number of sessions.
tls = session.get(f"{API}/tlsrpt/summary").json()
if tls["success_rate"] is not None and tls["success_rate"] < 99:
print(f'TLS success {tls["success_rate"]} %:', [(f["result_type"], f["session_count"]) for f in tls["top_failures"]])
A sudden drop usually means an expired certificate on one of your MX, or an MTA-STS policy that no longer matches them.
Limits and errors
| Situation | Answer |
|---|---|
| Too many calls on an endpoint | 429, with a Retry-After header |
Recommendation id unknown on the account | 404 |
| Missing or invalid key | 401 |
| Plan without API access | 403 |