# Domain and Email Health (`vizellie/domain-email-health`) Actor

Checks MX, SPF, DMARC, DKIM, MTA-STS, TLS-RPT, BIMI, DNSSEC and domain expiry for a list of domains. One row per domain with a score and a plain fix for each problem. $0.003 per domain. It does not guarantee that email gets delivered.

- **URL**: https://apify.com/vizellie/domain-email-health.md
- **Developed by:** [Vi Zellie](https://apify.com/vizellie) (community)
- **Categories:** Developer tools, Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 domain checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Domain and Email Health

Check the mail and DNS setup of public domains. Each domain gets one row: a score from 0 to 100, a letter grade, and a short list of fixes in plain language.

Not legal advice; general information only.

These findings flag likely issues. This is not a deliverability guarantee or a security certification.

### What you get

For each public domain:

- Whether MX records are present, including a null MX that means the domain does not accept mail.
- SPF: whether a record exists, whether there is only one, whether it stays within the DNS lookup limit, and whether it ends in `-all` or `~all`.
- DMARC: whether a record exists, the policy (`none`, `quarantine` or `reject`), the percent it covers, and whether an aggregate report destination is set. The destination address is not saved.
- DKIM: whether a record was found for each common selector, plus any extra selectors you add. A record with an empty key is revoked and does not count as found. Key material is not saved. If none of the common selectors has a key, the note says that a custom selector may be a false alarm.
- Whether MTA-STS, TLS-RPT and BIMI records are present.
- Whether DNSSEC is enabled.
- The domain expiry date and the registrar name, when the registry publishes them.

A domain that expires within **Domain expiry warning** days is called out, so a weekly schedule still leaves time to renew. The default is 21 days.

Unreachable domains are stored with no score and are not charged. Invalid names are skipped and are not charged. Private addresses, internal names, and addresses on a port other than 80 or 443 are skipped and are not charged. The summary counts every name you supplied, how many were accepted, and how many of those unsafe names were skipped. A run whose names are all private still finishes, with nothing checked.

### What is not collected

Email addresses are never saved. That includes DMARC report destinations and anything in a registry contact record. Registrant names, postal addresses and phone numbers are not saved. DKIM public keys are not saved. DNS answers are not saved.

### Tutorial

1. Paste domain names under **Domains**. Use the name that sends mail, usually the apex, such as `example.com`.
2. If your mail provider uses a selector that is not in the common list, add it under **Extra DKIM selectors**. Up to 10 names. Each one is a single DNS label.
3. Leave **Domain expiry warning** at 21 for a weekly run, or raise it if you want earlier notice.
4. Set **Max domains** if you want a hard cap on the bill.
5. Start the run. Open the dataset for one row per domain, or the **REPORT** record in the key-value store for the same findings as a page.

#### Weekly monitor

Create a schedule (for example every Monday) and save the input with your domain list. Each run charges the start fee plus one domain fee for every domain that resolved. A domain that does not resolve is listed and is free.

### Pricing

You pay a small fee when the run starts, plus a fee for each public domain that could be checked.

| Event | Price |
| --- | --- |
| Run start | $0.00005 |
| Domain checked | $0.003 |

**Examples**

- 1 domain: $0.00305
- 10 domains: $0.03005
- A run where every domain is invalid or does not resolve: $0.00005

### Input

| Field | Default | What it does |
| --- | --- | --- |
| Domains | empty | Public domain names to check. One row each. |
| Domain expiry warning (days) | 21 | Flag domains that expire soon. |
| Max domains | 10 | Cap how many domains one run will check. |
| Concurrency | 2 | How many domains to check at the same time. |

### Output

The overview table shows the score, the grade, MX, the SPF qualifier, the DMARC policy, how many DKIM selectors were found, DNSSEC, and the expiry date. The item also lists the fixes, highest impact first.

### Limits

Up to 50 domains in one run. Lookups use public DNS. The registry expiry lookup follows only public HTTPS redirects.

### Disclaimer

These findings flag likely mail and DNS issues. This is not a deliverability guarantee or a security certification. A high score does not mean every message will be accepted.

# Actor input Schema

## `domains` (type: `array`):

Public domain names to check, for example example.com. One result is returned per domain. Private and internal names are skipped and are not charged.

## `dkimSelectors` (type: `array`):

Optional extra DKIM selector names, checked in addition to the common ones. Up to 10. Each name is a single DNS label, such as s1.

## `expiryWarningDays` (type: `integer`):

Flag a domain that expires within this many days. The default of 21 suits a weekly schedule.

## `maxDomains` (type: `integer`):

Maximum number of domains to check in one run.

## `concurrency` (type: `integer`):

How many domains to check at the same time.

## Actor input object example

```json
{
  "domains": [
    "example.com"
  ],
  "dkimSelectors": [],
  "expiryWarningDays": 21,
  "maxDomains": 10,
  "concurrency": 2
}
```

# Actor output Schema

## `domains` (type: `string`):

One dataset row per domain.

## `summary` (type: `string`):

The OUTPUT record in the default key-value store.

## `report` (type: `string`):

The REPORT record in the default key-value store.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "domains": [
        "example.com"
    ],
    "dkimSelectors": [],
    "expiryWarningDays": 21,
    "maxDomains": 10,
    "concurrency": 2
};

// Run the Actor and wait for it to finish
const run = await client.actor("vizellie/domain-email-health").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "domains": ["example.com"],
    "dkimSelectors": [],
    "expiryWarningDays": 21,
    "maxDomains": 10,
    "concurrency": 2,
}

# Run the Actor and wait for it to finish
run = client.actor("vizellie/domain-email-health").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "domains": [
    "example.com"
  ],
  "dkimSelectors": [],
  "expiryWarningDays": 21,
  "maxDomains": 10,
  "concurrency": 2
}' |
apify call vizellie/domain-email-health --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vizellie/domain-email-health"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/hlBRX3hCBCdkqEeXF/builds/zTXKpvPQqoyVJRhnb/openapi.json
