# Vendor Security Posture - DMARC, TLS, Headers & security.txt (`lentic_labs/domain-email-tls-check`) Actor

Outside-in configuration check per vendor domain for TPRM and procurement reviews: DMARC/SPF/DKIM, TLS certificate, HTTP security headers and security.txt, with a 0-100 checklist score, grade and fix list. Passive: DNS + 2 HTTPS requests. $4 per 1,000 domains.

- **URL**: https://apify.com/lentic_labs/domain-email-tls-check.md
- **Developed by:** [Lentic Labs](https://apify.com/lentic_labs) (community)
- **Categories:** Developer tools, Business, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 vendor 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Vendor Security Posture - DMARC, TLS, Headers & security.txt

**A quick, passive, outside-in configuration check of every vendor on your list, for third-party risk (TPRM) and procurement reviews.** Give a list of supplier domains and get one compact row per domain with:

- A **0-100 score**, letter **grade** and `reviewPriority` (how soon to follow up), from a **published rubric** (below), on every fully checked row
- An email-only `emailScore` on every row, and `webStatus` showing whether the vendor's website could be checked from Apify's servers
- A **fix list** ordered by points, e.g. `+12: Enforce DMARC: move from p=none to p=quarantine or p=reject at pct=100`
- **Email spoofing protection:** DMARC policy, SPF, DKIM, and the DNS side of the Gmail/Yahoo bulk-sender rules
- **TLS:** certificate status, days to expiry, issuer and TLS version
- **HTTP security headers:** HSTS, Content-Security-Policy, X-Frame-Options / frame-ancestors, X-Content-Type-Options, Referrer-Policy
- **security.txt (RFC 9116):** present, has a Contact, Expires date, signed. Contact values are never output.
- Mail provider and email security gateway (Microsoft 365, Google Workspace, Proofpoint, Mimecast…)

Use it to pre-screen vendors before a questionnaire, to evidence outside-in checks in a vendor review, or to re-check your supplier list on a schedule.

### Example output

```json
{ "domain": "example-vendor.com", "status": "ok", "score": 66, "grade": "D", "reviewPriority": "medium", "emailScore": 88,
  "scoreCoverage": ["emailAuth", "tls", "headers", "securityTxt"], "webStatus": "ok",
  "scoreBreakdown": { "emailAuth": "35/40", "tls": "25/25", "headers": "6/25", "securityTxt": "0/10" },
  "fixes": ["+10: Publish /.well-known/security.txt (RFC 9116) with Contact and Expires", "+6: Send a Content-Security-Policy header", "+5: Add a DMARC rua= reporting address", "..."],
  "spoofable": false, "dmarcPolicy": "reject", "tlsCertStatus": "ok", "tlsDaysLeft": 71, "hsts": true, "csp": null, "securityTxtFound": false }
```

A vendor whose website blocks Apify's servers gets a **partial** row instead of a grade:

```json
{ "domain": "blocking-vendor.com", "status": "partial", "score": null, "grade": null, "reviewPriority": null, "emailScore": 100,
  "scoreCoverage": ["emailAuth"], "webStatus": "blocked-by-site",
  "scoreBreakdown": { "emailAuth": "40/40", "tls": "not assessed", "headers": "not assessed", "securityTxt": "not assessed" },
  "note": "HTTPS could not be fully checked from Apify's servers (HTTP 403). TLS, headers and security.txt were not assessed; this is not evidence the vendor lacks them." }
```

Values are illustrative.

### Scoring rubric (0-100)

| Category | Check | Points |
|---|---|---|
| Email authentication (40) | DMARC enforced (p=quarantine or p=reject, pct=100); DMARC p=none earns 8 | 20 |
| | SPF valid (at most 10 lookups) and not `+all` | 10 |
| | DKIM key found among 30 common selectors. Not found means "not verified" (a custom selector may exist): these 5 points are left out of the maximum, not deducted | 5 |
| | DMARC `rua=` reporting address | 5 |
| TLS (25) | Certificate valid: trusted chain, covers the hostname, in date | 15 |
| | More than `warnDays` (default 30) days to expiry | 5 |
| | TLS 1.2 or 1.3 negotiated | 5 |
| HTTP security headers (25) | HSTS max-age at least 180 days (shorter: 4) | 8 |
| | Content-Security-Policy enforced (report-only: 2) | 6 |
| | Clickjacking protection (X-Frame-Options DENY/SAMEORIGIN or CSP frame-ancestors) | 4 |
| | X-Content-Type-Options: nosniff | 4 |
| | Referrer-Policy set (not unsafe-url) | 3 |
| security.txt (10) | File with a Contact field (file without Contact: 2) | 6 |
| | Expires present and in the future | 4 |

**About the score.** The score, grade and review priority summarize a small set of public, outside-in configuration checks (email-authentication DNS records, the homepage TLS certificate and response headers, and security.txt) as seen from Apify's servers at the time of the run. They are not a security rating, certification or audit, and say nothing about a vendor's internal controls, data handling or breach history. Results can be incomplete or wrong. For example, a vendor may block automated traffic, use a DKIM selector this tool doesn't try, or send different headers to browsers. Rows that couldn't be fully checked are marked `partial` and are not graded. Confirm findings with the vendor before relying on them, and don't publish scores about a third party as a statement about its security.

Grades: A ≥ 90, B ≥ 80, C ≥ 70, D ≥ 60, F < 60. The score is the points earned out of the points available for the categories assessed, scaled to 100. `emailScore` is the email-authentication category alone, scaled to 100, and is returned on every checked row.

`reviewPriority` is how soon to follow up with the vendor, not a measure of risk. It is null on partial rows and when web checks are off. Otherwise it is `high` when the score is under 60, DMARC is missing or invalid, or the certificate is expired or invalid; `medium` when the score is under 80, the domain is spoofable (DMARC not enforced) or the certificate expires soon; otherwise `low`.

#### Partial rows (website not fully checkable)

`webStatus` says how the homepage looked from Apify's servers:

- `ok`: it answered normally (a 404 or 410 homepage still counts as answered).
- `blocked-by-site`: the final response was any 4xx except 404 and 410 (for example 401, 403, 417 or 429), any 5xx, or a challenge or bot-block page (a `cf-mitigated`, `x-amzn-waf-action` or `x-sucuri-block` header, or a bot-check page title such as "Just a moment…" or "Access Denied" on a 200). These rows get **no web subscore**: TLS, headers and security.txt are all `not assessed`.
  Challenge pages are recognised from the status code, a few WAF headers and the page title; a bot-check page that looks like a normal page can still be scored. The homepages of CAPTCHA and DDoS-protection vendors themselves may come back as `blocked-by-site` because their own page titles use those words; such a row is free and has no grade.
- `partial`: only the TLS certificate was obtained (for example the homepage redirects to another site or to http, or the connection dropped after the handshake). TLS is scored, since an expired, mismatched or untrusted certificate is a real finding; the `note` says headers and security.txt not assessed, and no grade is given. `redirectsTo` gives the host of a redirect that wasn't followed.
- `unreachable`: timeout, connection refused or reset, host unreachable, or a TLS handshake failure with no certificate.
- `skipped`: you turned web checks off (see below).

Whenever web checks were on and the homepage wasn't `ok`, the row has `status: "partial"`: `score`, `grade` and `reviewPriority` are null, the categories that weren't checked read `not assessed` in `scoreBreakdown` with their fields null and no fixes, `scoreCoverage` lists what was assessed, and `note` explains why. **Partial rows are not charged.**

security.txt counts as missing only on a 404 or 410, or when the server returns a page that isn't a security.txt (for example an HTML "not found" page with status 200). If the security.txt request errors, times out, is blocked or redirects elsewhere, it is `not assessed` and the row is partial.

With **Check TLS, security headers and security.txt** off, `webStatus` is `skipped` and the row's `note` says "Web checks were turned off: grade based on email checks only." The score and grade are then based on email checks only (scaled to 100), `scoreBreakdown` and `scoreCoverage` show just that category, and `reviewPriority` is null.

### How much does it cost?

**$0.004 per domain ($4 per 1,000).** No subscription and no start fee. You're charged once per domain that was scored: fully checked, or email-only when you turn web checks off. Partial rows (website not fully checkable from Apify's servers), invalid, duplicate, non-existent (NXDOMAIN) and DNS-failure domains are free. Set a maximum total charge per run and the Actor stops at your budget.

On Apify's free plan, the $5 monthly usage credit covers up to about 1,250 domains a month (less if you use other Apify Actors or services).

#### How it compares

Multi-check domain posture Actors in the Apify Store (checked Oct 26, 2026) charge about $0.004 to $0.15 per domain, several with a start fee of $0.005 or more; most charge $0.006 to $0.03. Single-purpose header, security.txt or DMARC checkers charge $0.0005 to $0.02 each. This Actor covers email authentication, TLS, headers and security.txt in one row at the low end of that range, with a published rubric and no start fee.

### How does it work?

All checks are passive:

- **Public DNS:** MX, SPF (with include chain), DMARC, 30 common DKIM selectors.
- **At most two HTTPS requests per domain:** `GET https://<domain>/` (the certificate is read from this request's connection and the response headers are checked; on a 200 response only the first 8 KB of the page are read, to recognise a bot-check page by its title, and nothing from the page is stored) and `GET https://<domain>/.well-known/security.txt` (up to 32 KB; requested only when the homepage answered normally). Within each, redirects to the same registrable domain (e.g. apex → www) are followed, at most 2 hops.

There's no port scanning, crawling, login, form submission or vulnerability probing, and no email is sent. Private, loopback and cloud-metadata addresses are refused in every IPv4 and IPv6 form, and the connection goes only to the address that was checked. Each HTTPS request has a hard time limit. Requests identify themselves with a `LenticLabs-Actor` User-Agent. Use it only on domains you own, vendors you're assessing as part of your own risk process, or for research on public DNS and web data. No personal data is collected: security.txt Contact values aren't stored, and only whether a Contact exists is reported.

Gmail and Yahoo also require alignment, one-click unsubscribe and low spam rates; Microsoft recommends similar practices. These can only be seen in real mail. This checks the DNS side only.

Every row has a `rowId`, stable per input item. In the rare case a dataset save times out after the row was stored, an uncharged `error` row with the same `rowId` follows; keep the non-error row for each `rowId`.

### Use from AI agents (Apify MCP)

Add `https://mcp.apify.com?tools=lentic_labs/domain-email-tls-check` to your MCP client and ask *"Check DMARC, TLS, headers and security.txt for these 20 vendors and list the top fixes."* Agents without an Apify account can use Apify's agentic payments (experimental): buy a prepaid token with x402, or pay per request with Skyfire.

### Input compatibility

The older `checkTls` input is still accepted as an alias of `checkWeb`.

### Data sources and attribution

No third-party dataset is used: every result comes from public DNS and from the vendor's own HTTPS responses at run time. The security.txt format is defined in RFC 9116 (IETF). Header guidance follows common public recommendations such as the OWASP Secure Headers Project; this Actor is not affiliated with or endorsed by OWASP or the IETF.

### Need more depth?

- Email-security audit with MTA-STS, TLS-RPT and BIMI: [DMARC Checker Bulk - SPF, DKIM & Email Security](https://apify.com/lentic_labs/email-dmarc-security-prospector)
- Single-purpose tools: [Email Provider Lookup - Bulk MX for Microsoft 365 & Google](https://apify.com/lentic_labs/mail-provider-detector), [DMARC & SPF bulk-sender check](https://apify.com/lentic_labs/dmarc-spf-bulk-sender-check), [SSL certificate expiry checker](https://apify.com/lentic_labs/ssl-certificate-expiry-checker)

***

Microsoft 365 and Outlook are trademarks of Microsoft Corporation. Google Workspace and Gmail are trademarks of Google LLC. Yahoo is a trademark of Yahoo. Proofpoint and Mimecast are trademarks of their respective owners. Names are used only to describe the providers checked; Lentic Labs is not affiliated with or endorsed by them.

# Changelog

This Actor's version history is a separate document: https://apify.com/lentic_labs/domain-email-tls-check/changelog.md

# Actor input Schema

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

Vendor or supplier domains. URLs and emails are reduced to the domain; www. is removed; duplicates are skipped.

## `domainsText` (type: `string`):

Optional: paste a vendor list separated by newlines, commas or spaces.

## `checkWeb` (type: `boolean`):

Off: DNS-only (email authentication). Rows then have webStatus "skipped" and the grade is based on email checks only.

## `checkDkim` (type: `boolean`):

Looks up 30 common DKIM selectors (DNS only).

## `warnDays` (type: `integer`):

Certificates expiring within this many days lose 5 points and are marked expiring-soon.

## `timeoutMs` (type: `integer`):

Per-request timeout.

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

Stop after this many unique domains (0 = no limit). Also limit cost with the run's maximum charge.

## `maxConcurrency` (type: `integer`):

Domains checked in parallel.

## Actor input object example

```json
{
  "domains": [
    "github.com",
    "stripe.com",
    "atlassian.com",
    "example.com"
  ],
  "checkWeb": true,
  "checkDkim": true,
  "warnDays": 30,
  "timeoutMs": 10000,
  "maxDomains": 0,
  "maxConcurrency": 10
}
```

# Actor output Schema

## `results` (type: `string`):

Default dataset, overview view.

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

Counts (charged, partial, free), web status, average score, grades and review priorities.

# 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": [
        "github.com",
        "stripe.com",
        "atlassian.com",
        "example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lentic_labs/domain-email-tls-check").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": [
        "github.com",
        "stripe.com",
        "atlassian.com",
        "example.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("lentic_labs/domain-email-tls-check").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": [
    "github.com",
    "stripe.com",
    "atlassian.com",
    "example.com"
  ]
}' |
apify call lentic_labs/domain-email-tls-check --silent --output-dataset

```

## MCP server setup

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

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/fFS4GMpkw8MUJSMG2/builds/TkzQ2hSvcsEZTVcaY/openapi.json
