---
title: "Have an AI agent send you a weekly deliverability report"
description: "Read Unitpost sending stats and domain DNS health over MCP or the CLI, have a model summarize bounces, complaints and DNS issues, and email the team weekly."
url: https://www.unitpost.com/blog/agents/ai-agent-deliverability-report
section: Blog
updated: 2026-11-02
---
# Have an AI agent send you a weekly deliverability report

A weekly deliverability report takes three reads: email_stats for sends, hard and soft bounces, complaints, suppressions and unsubscribes, email_domains_list for each domain's status, and email_domains_diagnose for DNS findings with their fixes. A key with emails:read, domains:read and emails:send covers it on any plan. Run it from a GitHub Actions schedule: the CLI reads, a model writes the summary, and the CLI emails it to the team.

## Have an AI agent send you a weekly deliverability report

> Read Unitpost sending stats and domain DNS health over MCP or the CLI, have a model summarize bounces, complaints and DNS issues, and email the team weekly.

A deliverability report answers two questions: did last week's mail land, and is anything about to stop it. Unitpost answers each with one read, available as an MCP tool, a CLI command and a REST endpoint. The agent does the part people skip: turning numbers into a few sentences someone acts on.

### What should the report read?

- `email_stats` (`GET /v1/email/stats`, `unitpost email stats`, `emails:read`). Totals for `days`, or a `from` and `to` period: `sent`, `delivered`, `bounced` split into `bounced_permanent` (hard) and `bounced_transient` (soft), `complained`, `failed`, `suppressed` (sends that skipped a suppressed recipient) and `unsubscribed`. Delivery, bounce and complaint rates are against sent; open and click rates count only mail sent with that tracking on.
- `email_domains_list` (`GET /v1/email/domains`, `domains:read`). Each domain's `status`: `pending`, `verifying`, `verified`, `degraded` or `failed`. A `degraded` domain failed a re-check: it keeps sending through a 24-hour grace, then stops.
- `email_domains_diagnose` (`GET /v1/email/domains/{id}/diagnose`, `domains:read`). It asks every authoritative nameserver about every record the domain needs and returns `findings`, most serious first, each with a `code` such as `record_missing` or `dmarc_missing`, a `severity` of `blocking`, `warning` or `info`, and the `fix`. It shares the DNS rate limit of verify, so call it once per domain per run.
- `sms_stats` if you also text (`sms:read`): delivered, failed, suppressed and segments billed. No opens or clicks.

A hard bounce or a complaint already suppresses that address, so the report isn't a cleanup list. It tells you where bad addresses come from and whether a domain is drifting.

### Which key does the agent need?

`emails:read`, `domains:read` and `emails:send`. The Sending only preset, which every plan can create, has all three, plus `campaigns:send` and reads such as contacts, segments and templates. On a paid plan, create a custom key with just the three, and `sms:read` if you want SMS numbers.

Leave out `domains:write`. The agent can see a broken record but can't verify, change or delete a domain.

### How do I ask an MCP client for it?

For a one-off report, connect your client to `https://mcp.unitpost.com/mcp?toolsets=sending,domains`, which lists `email_stats`, `email_send` and the `email_domains_*` tools instead of the full default set. The list only saves the model's context; the credential's scopes decide what can run. Then ask:

```bash
Read email_stats for the last 7 days and the 7 days before. Run
email_domains_diagnose once on each of my domains. Email a plain-text
summary to team@yourdomain.com from reports@yourdomain.com: what changed,
bounces, complaints, and each blocking or warning finding with its fix.
```

That works with a person watching. On a schedule, split the job.

### How do I run it every Monday?

This GitHub Actions workflow reads with the CLI, has Claude Code write the summary with no tools at all, and sends with the CLI. Each CLI command is the same API operation the MCP tool calls.

```bash
name: Weekly deliverability report

on:
  schedule:
    - cron: "17 8 * * 1"
      timezone: "America/New_York"
  workflow_dispatch:

permissions:
  contents: read

jobs:
  report:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v7
        with:
          node-version: "22"
      - run: npm install -g unitpost-cli @anthropic-ai/claude-code

      - name: Read stats and domain health
        env:
          UNITPOST_API_KEY: ${{ secrets.UNITPOST_REPORT_KEY }}
        run: |
          unitpost email stats --days 7 > this-week.json
          unitpost email stats --from "$(date -u -d '14 days ago' +%FT%TZ)" \
            --to "$(date -u -d '7 days ago' +%FT%TZ)" > last-week.json
          unitpost email domains list --limit 100 > domains.json
          for id in $(jq -r '.data[].id' domains.json); do
            unitpost email domains diagnose "$id"
          done | jq -s '.' > dns.json

      - name: Write the summary
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          PROMPT: >-
            Write a plain-text weekly deliverability report for the team
            from this Unitpost JSON. No Markdown. Say what changed since
            last week first. Use only numbers that appear in the JSON and
            don't guess at causes. List each domain that isn't verified,
            and each blocking or warning finding with its fix.
        run: |
          jq -s '{this_week: .[0], last_week: .[1], domains: .[2], dns: .[3]}' \
            this-week.json last-week.json domains.json dns.json |
            claude --bare -p "$PROMPT" --tools "" > report.txt

      - name: Email the team
        env:
          UNITPOST_API_KEY: ${{ secrets.UNITPOST_REPORT_KEY }}
        run: |
          unitpost email send \
            --from "Deliverability <reports@yourdomain.com>" \
            --to team@yourdomain.com \
            --subject "Deliverability report, week of $(date -u +%F)" \
            --text-file report.txt \
            --idempotency-key "deliverability-$(date -u +%G-W%V)"
```

What each part is doing:

- The schedule. Per GitHub's workflow syntax (https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax), `cron` is UTC unless you add an IANA `timezone`. Scheduled runs can be delayed (https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows) at busy times such as the top of the hour, hence minute 17, and a public repository's schedules turn off after 60 days without activity. `workflow_dispatch` lets you run it by hand.
- Keys per step. The Unitpost key is only in the CLI steps, the Anthropic key only in the summary step. With `--bare` and `--tools ""`, Claude Code in print mode (https://code.claude.com/docs/en/headless) loads no MCP servers and has no tools: it reads the JSON on stdin and writes text. The model never holds a Unitpost credential and can't change the recipients.
- JSON without `--json`. When stdout isn't a terminal, the CLI prints exactly one JSON document.
- The idempotency key names the ISO week. A re-run within 24 hours returns the first send, and if it wrote a different summary, the key answers `409` and nothing goes out.
- The From address must be on a domain verified in the key's workspace, or the send answers `403 domain_not_verified`.

### What makes the summary worth reading?

Change, not totals. Hard bounces jumping since last week sends someone to check the signup form; a delivered total alone sends nobody anywhere. That is why the workflow reads the week before too.

Keep each `fix` as Unitpost wrote it. It is the concrete next step for that finding, and a paraphrase can drop the detail that matters.

Tell the model not to explain. It has counts, not causes, and a guessed cause sends people the wrong way.

### Is a weekly report enough?

Not for a broken domain. `degraded` lasts 24 hours before sends stop, and a weekly report can be six days late. Subscribe a webhook to `domain.updated`, which fires on every status change and carries `previous_status`, and to `email.complained` if complaints need a same-day look. The report is for trends; the webhook is the alarm.

### How do I check the DNS myself?

`email_domains_diagnose` reads the records Unitpost expects from your own nameservers. To see what any receiver sees, use the email domain checker (/tools/domain-checker), which grades SPF, DKIM, DMARC and MX and links each lost point to its fix, and the DMARC checker (/tools/dmarc-checker), which reads your policy and where reports go. Both run in your browser with no sign-up, for any domain.

## FAQ

### What should a weekly email deliverability report include?

Sent and delivered counts, hard and soft bounces, complaints, suppressed sends and unsubscribes, each compared with the week before. Then every sending domain's status and each blocking or warning DNS finding with its fix. Open rates are the weakest line: they count only mail sent with open tracking on.

### Can the agent fix the DNS problems it finds?

Not with this key, and it shouldn't. email_domains_diagnose is read-only, and the fix is a record at your DNS provider, which Unitpost can't edit. Once someone changes the record, verify the domain from the dashboard or with email_domains_verify, which needs domains:write.

### Can I run the report without GitHub Actions?

Yes. The steps are shell commands, so cron on a server, or any scheduler that can run the unitpost CLI with the key in its environment, does the same reads and the same send.

## Related

- [How should an AI agent handle email API rate limits?](https://www.unitpost.com/blog/agents/ai-agent-email-rate-limits): What a 429 from an email API means for an AI agent, how to read Retry-After and the RateLimit headers, and how to retry without sending anything twice.
- [How to have an AI agent reply to inbound email on your own domain](https://www.unitpost.com/blog/agents/ai-agent-reply-to-inbound-email): Point MX at a subdomain, verify the signed email.received webhook, dedupe, then send one threaded reply with an idempotency key from the inbound id.
- [How to send appointment reminders by SMS from an AI agent](https://www.unitpost.com/blog/agents/ai-agent-sms-appointment-reminders): Let an AI agent text appointment reminders: consent on record, a registered use case, a fixed template, the local hour, and one idempotency key per reminder.
