Nimbryxo Studio

Bulk Email Deliverability & SMTP Verification

Check email addresses for syntax, MX records, disposable and role-based domains, and optionally SMTP mailbox existence and catch-all behavior, with a verdict and confidence score per address.

Input schema

Input fields, types, required flags, descriptions, defaults, and prefills come from the published definition. Prefill examples are distinct from defaults.

FieldTypeRequiredDescription
emailsarrayoptionalOne email address per item. You can paste a list here or use the text box below; if both are filled, the actor verifies both. Duplicates are removed case-insensitively.
emailsTextstringoptionalPaste a list separated by new lines, commas, semicolons or spaces. Useful for CSV exports and email lists.
datasetIdstringoptionalOptional Apify dataset ID. The actor reads one email per item from datasetEmailField, verifies those addresses, and can carry the original columns into each record.
Show all input fields
FieldTypeRequiredDescription
emailsarrayoptionalOne email address per item. You can paste a list here or use the text box below; if both are filled, the actor verifies both. Duplicates are removed case-insensitively.
emailsTextstringoptionalPaste a list separated by new lines, commas, semicolons or spaces. Useful for CSV exports and email lists.
datasetIdstringoptionalOptional Apify dataset ID. The actor reads one email per item from datasetEmailField, verifies those addresses, and can carry the original columns into each record.
datasetEmailFieldstringoptionalColumn name that holds the email address when datasetId is set.
includeSourceFieldsbooleanoptionalWhen reading from a dataset, include the original row in sourceData so you can join the verification result back to your CRM export.
verificationDepthstringoptionalBasic checks syntax, MX records and the A/AAAA fallback. Standard adds disposable, role-based, free-provider and domain-health checks. Deep adds a best-effort SMTP mailbox probe and catch-all detection.
concurrencyintegeroptionalHow many addresses are checked in parallel. Higher is faster, but mail servers may rate-limit SMTP probes.
maxEmailsintegeroptionalUpper bound on unique addresses to verify in one run. Anything beyond this is skipped and reported in the run summary.
maxEmailsPerDomainintegeroptionalCap on addresses verified for the same domain. Extra addresses get an 'unknown' record explaining the cap instead of hammering one mail server.
smtpTimeoutnumberoptionalConnect and response timeout for each SMTP probe. Use a lower value for fast scans and a higher value for slow mail servers.
maxRetriesintegeroptionalExtra attempts for temporary DNS, connection and SMTP 4xx failures, using exponential backoff.
checkSmtpbooleanoptionalRun the best-effort SMTP mailbox probe even at basic or standard depth. Deep depth already includes it.
checkCatchAllbooleanoptionalProbe a random address to detect catch-all domains. Implies SMTP probing. Deep depth already includes it.
includeDomainHealthbooleanoptionalAdd SPF, DMARC policy and common DKIM selector findings for each domain at standard or deep depth.

Download the published input schema

View complete JSON
{
  "properties": {
    "checkCatchAll": {
      "default": false,
      "description": "Probe a random address to detect catch-all domains. Implies SMTP probing. Deep depth already includes it.",
      "editor": "checkbox",
      "prefill": false,
      "title": "Detect Catch-all Domains",
      "type": "boolean"
    },
    "checkSmtp": {
      "default": false,
      "description": "Run the best-effort SMTP mailbox probe even at basic or standard depth. Deep depth already includes it.",
      "editor": "checkbox",
      "prefill": false,
      "title": "Check SMTP Mailbox",
      "type": "boolean"
    },
    "concurrency": {
      "default": 10,
      "description": "How many addresses are checked in parallel. Higher is faster, but mail servers may rate-limit SMTP probes.",
      "editor": "number",
      "maximum": 50,
      "minimum": 1,
      "prefill": 10,
      "title": "Concurrency",
      "type": "integer"
    },
    "datasetEmailField": {
      "default": "email",
      "description": "Column name that holds the email address when datasetId is set.",
      "editor": "textfield",
      "prefill": "email",
      "title": "Dataset email field",
      "type": "string"
    },
    "datasetId": {
      "description": "Optional Apify dataset ID. The actor reads one email per item from datasetEmailField, verifies those addresses, and can carry the original columns into each record.",
      "editor": "textfield",
      "title": "Read emails from an existing dataset",
      "type": "string"
    },
    "emails": {
      "description": "One email address per item. You can paste a list here or use the text box below; if both are filled, the actor verifies both. Duplicates are removed case-insensitively.",
      "editor": "stringList",
      "items": {
        "type": "string"
      },
      "prefill": [
        "test@gmail.com"
      ],
      "title": "Email Addresses",
      "type": "array"
    },
    "emailsText": {
      "description": "Paste a list separated by new lines, commas, semicolons or spaces. Useful for CSV exports and email lists.",
      "editor": "textarea",
      "title": "Email Addresses (text)",
      "type": "string"
    },
    "includeDomainHealth": {
      "default": true,
      "description": "Add SPF, DMARC policy and common DKIM selector findings for each domain at standard or deep depth.",
      "editor": "checkbox",
      "prefill": true,
      "title": "Include Domain Health",
      "type": "boolean"
    },
    "includeSourceFields": {
      "default": false,
      "description": "When reading from a dataset, include the original row in sourceData so you can join the verification result back to your CRM export.",
      "prefill": false,
      "title": "Carry source dataset columns",
      "type": "boolean"
    },
    "maxEmails": {
      "default": 10000,
      "description": "Upper bound on unique addresses to verify in one run. Anything beyond this is skipped and reported in the run summary.",
      "editor": "number",
      "maximum": 100000,
      "minimum": 1,
      "prefill": 10000,
      "title": "Maximum Emails",
      "type": "integer"
    },
    "maxEmailsPerDomain": {
      "default": 100,
      "description": "Cap on addresses verified for the same domain. Extra addresses get an 'unknown' record explaining the cap instead of hammering one mail server.",
      "editor": "number",
      "maximum": 10000,
      "minimum": 1,
      "prefill": 100,
      "title": "Maximum Emails per Domain",
      "type": "integer"
    },
    "maxRetries": {
      "default": 2,
      "description": "Extra attempts for temporary DNS, connection and SMTP 4xx failures, using exponential backoff.",
      "editor": "number",
      "maximum": 5,
      "minimum": 0,
      "prefill": 2,
      "title": "Max Retries",
      "type": "integer"
    },
    "smtpTimeout": {
      "default": 10,
      "description": "Connect and response timeout for each SMTP probe. Use a lower value for fast scans and a higher value for slow mail servers.",
      "editor": "number",
      "maximum": 60,
      "minimum": 1,
      "prefill": 10,
      "title": "SMTP Timeout (seconds)",
      "type": "number"
    },
    "verificationDepth": {
      "default": "standard",
      "description": "Basic checks syntax, MX records and the A/AAAA fallback. Standard adds disposable, role-based, free-provider and domain-health checks. Deep adds a best-effort SMTP mailbox probe and catch-all detection.",
      "editor": "select",
      "enum": [
        "basic",
        "standard",
        "deep"
      ],
      "enumTitles": [
        "Basic (syntax + MX)",
        "Standard (+ disposable, role, free provider, domain health)",
        "Deep (+ SMTP mailbox probe and catch-all)"
      ],
      "prefill": "basic",
      "title": "Verification Depth",
      "type": "string"
    }
  },
  "schemaVersion": 1,
  "title": "Email Verifier Input",
  "type": "object"
}

Output schema

Published result links and storage locations. This schema is not an exhaustive contract for every field in a dataset record.

FieldTypeRequiredDescription
resultsstringoptionalDataset containing one verification record per unique email address, with only the fields that were actually measured.
summarystringoptionalKey-value store record named SUMMARY with input, truncation, per-domain cap and verdict totals for the run.

Download the published output schema

View complete JSON
{
  "actorOutputSchemaVersion": 1,
  "description": "One record per unique email address with verdict, confidence, checks that actually ran, MX and provider details, typo suggestion, alias flags, SMTP response details, catch-all confidence, domain health and a recommended next action.",
  "properties": {
    "results": {
      "description": "Dataset containing one verification record per unique email address, with only the fields that were actually measured.",
      "template": "{{links.apiDefaultDatasetUrl}}/items",
      "title": "Email Verification Results",
      "type": "string"
    },
    "summary": {
      "description": "Key-value store record named SUMMARY with input, truncation, per-domain cap and verdict totals for the run.",
      "template": "{{links.apiDefaultKeyValueStoreUrl}}/records/SUMMARY",
      "title": "Batch Summary",
      "type": "string"
    }
  },
  "title": "Bulk Email Deliverability & SMTP Verification Output",
  "type": "object"
}

Dataset schema

Published dataset views and any declared field definitions. A view can select only a subset of the returned record fields.

Download the published dataset schema

View complete JSON
{
  "actorSpecification": 1,
  "fields": {},
  "views": {
    "detailed": {
      "display": {
        "component": "table",
        "properties": {
          "aRecordFallback": {
            "format": "boolean",
            "label": "A/AAAA Fallback"
          },
          "catchAllConfidence": {
            "format": "number",
            "label": "Catch-all Confidence"
          },
          "catchAllMethod": {
            "format": "text",
            "label": "Catch-all Method"
          },
          "checks": {
            "format": "object",
            "label": "Checks"
          },
          "confidence": {
            "format": "number",
            "label": "Confidence"
          },
          "didYouMean": {
            "format": "text",
            "label": "Did You Mean"
          },
          "domainExists": {
            "format": "boolean",
            "label": "Domain Exists"
          },
          "domainHealth": {
            "format": "object",
            "label": "Domain Health"
          },
          "email": {
            "format": "text",
            "label": "Email"
          },
          "isAlias": {
            "format": "boolean",
            "label": "Alias"
          },
          "mxHost": {
            "format": "text",
            "label": "MX Host"
          },
          "mxProvider": {
            "format": "text",
            "label": "MX Provider"
          },
          "mxRecords": {
            "format": "array",
            "label": "MX Records"
          },
          "normalizedEmail": {
            "format": "text",
            "label": "Normalized Email"
          },
          "plusTag": {
            "format": "boolean",
            "label": "Plus Tag"
          },
          "reason": {
            "format": "text",
            "label": "Reason"
          },
          "recommendedAction": {
            "format": "text",
            "label": "Action"
          },
          "riskLevel": {
            "format": "text",
            "label": "Risk"
          },
          "smtpAttempted": {
            "format": "boolean",
            "label": "SMTP Attempted"
          },
          "smtpCode": {
            "format": "number",
            "label": "SMTP Code"
          },
          "smtpMessage": {
            "format": "text",
            "label": "SMTP Message"
          },
          "smtpVerifiable": {
            "format": "boolean",
            "label": "SMTP Verifiable"
          },
          "verdict": {
            "format": "text",
            "label": "Verdict"
          },
          "verifiedAt": {
            "format": "text",
            "label": "Verified At"
          }
        }
      },
      "title": "Detailed",
      "transformation": {
        "fields": [
          "email",
          "normalizedEmail",
          "sourceData",
          "verdict",
          "confidence",
          "riskLevel",
          "recommendedAction",
          "checks",
          "mxHost",
          "mxRecords",
          "mxProvider",
          "aRecordFallback",
          "domainExists",
          "isAlias",
          "plusTag",
          "didYouMean",
          "smtpAttempted",
          "smtpVerifiable",
          "smtpCode",
          "smtpMessage",
          "catchAllConfidence",
          "catchAllMethod",
          "domainHealth",
          "reason",
          "verifiedAt"
        ]
      }
    },
    "overview": {
      "display": {
        "component": "table",
        "properties": {
          "confidence": {
            "format": "number",
            "label": "Confidence"
          },
          "didYouMean": {
            "format": "text",
            "label": "Did You Mean"
          },
          "email": {
            "format": "text",
            "label": "Email"
          },
          "mxProvider": {
            "format": "text",
            "label": "MX Provider"
          },
          "reason": {
            "format": "text",
            "label": "Reason"
          },
          "recommendedAction": {
            "format": "text",
            "label": "Action"
          },
          "riskLevel": {
            "format": "text",
            "label": "Risk"
          },
          "verdict": {
            "format": "text",
            "label": "Verdict"
          }
        }
      },
      "title": "Overview",
      "transformation": {
        "fields": [
          "email",
          "verdict",
          "confidence",
          "riskLevel",
          "recommendedAction",
          "mxProvider",
          "didYouMean",
          "reason"
        ]
      }
    }
  }
}

Key-value schema

Published key-value store definitions, when provided by this build.

This definition is not present in the default published build. See the published README for available documentation.

Published README

Read the full published documentation

Bulk Email Deliverability & SMTP Verification

Paste an email list and get one clean record per address: syntax, MX and A/AAAA routing, disposable and role-based detection, typo suggestions, SPF/DMARC/DKIM findings, a best-effort SMTP mailbox probe, catch-all detection, a risk level and a recommended next action.

Use it to clean a CRM export, screen sign-up addresses, scrub a lead list before outreach or periodically re-check stored contacts. No API keys, cookies or third-party verification service are required.

What it checks

  • Syntax and normalization - catches malformed addresses and returns the normalized form plus the local part and domain.
  • Domain routing - MX records sorted by priority, the primary mail host, the mail provider, and an A/AAAA fallback when a domain publishes no MX record. A null MX record (0 .) is treated as "does not accept email", not as a deliverability win.
  • Typo suggestions - flags likely mistakes such as gmial.com, hotmail.con or outlok.com with a corrected address in didYouMean.
  • Disposable, role and free providers - separate flags for throwaway domains, shared addresses such as admin@ or support@, and consumer providers such as Gmail or Outlook.
  • Alias signals - plusTag for +tag subaddressing and isAlias for plus tags and Gmail dot aliases.
  • Domain health - whether SPF and DMARC records exist, the DMARC policy, and which common DKIM selectors publish a usable key.
  • SMTP mailbox probe - a best-effort RCPT TO check against the domain's mail server with the raw code and message.
  • Catch-all detection - tests a random address at the domain and reports how confident the result is.
  • Risk and action - a riskLevel (low, medium, high) and a recommendedAction (send, send_with_caution, verify_manually, remove).
  • Batch summary - a key-value store record named SUMMARY with totals, truncation and per-domain cap counts. It is separate from the dataset, so one dataset row still means one unique email address.

Output per email

Field Type Description
email string The normalized address, or the trimmed input when syntax fails.
normalizedEmail string Normalized address (domain lowercased, IDN converted) when syntax passes.
localPart string The part before @.
domain string The domain that was checked.
verdict string valid, invalid, risky, disposable or unknown.
confidence number 0-100 confidence score for the verdict.
checks object Only checks that actually ran: syntax, mxRecord, aRecord, smtp, disposable, roleBased, freeProvider, catchAll.
mxHost string Highest-priority mail host.
mxRecords array MX records as {host, priority} objects, sorted by priority.
mxProvider string Recognized provider such as Google, Microsoft, Proton, Zoho or Fastmail.
aRecordFallback boolean true when there is no MX record and mail routing falls back to A/AAAA.
domainExists boolean Whether DNS shows the domain exists.
didYouMean string Suggested correction for a likely typo in the domain.
isAlias boolean true for plus-tag subaddressing or a Gmail dot alias.
plusTag boolean true when the local part contains +.
domainHealth object spf and dmarc booleans, dmarcPolicy when published, and dkimSelectors for common selectors with a usable key.
smtpAttempted boolean true when an SMTP probe was attempted.
smtpVerifiable boolean true when the server returned a mailbox-level RCPT response.
smtpCode number Raw SMTP response code from the mailbox probe.
smtpMessage string Raw SMTP response text (trimmed).
checks.catchAll boolean Whether the domain accepted a random address.
catchAllConfidence number 0-100 confidence in the catch-all result. A random-address acceptance scores 60 because some servers accept first and bounce later.
catchAllMethod string Method used for catch-all detection (smtp-random-recipient).
riskLevel string low, medium or high.
recommendedAction string send, send_with_caution, verify_manually or remove.
reason string Plain-language explanation when the verdict is not a clean valid.
verifiedAt string ISO 8601 UTC timestamp.

Fields that were not measured are omitted, not set to null. A false boolean is kept because it is a real answer.

Input

At least one of emails, emailsText, or datasetId is required. Apify's input-schema validator does not support anyOf, so this is enforced by the actor with a clear failure message.

Field Type Default Description
emails array of strings - Email addresses, one per item.
emailsText string - The same list as text, separated by new lines, commas, semicolons or spaces.
datasetId string - Optional existing dataset ID to read emails from. The dataset must be readable by the run.
datasetEmailField string email Column name that holds the address when datasetId is used.
includeSourceFields boolean false Carry the original dataset row in sourceData.
verificationDepth select standard basic, standard or deep.
concurrency integer 10 Parallel checks (1-50).
maxEmails integer 10000 Upper bound on unique addresses per run (1-100000).
maxEmailsPerDomain integer 100 Cap per domain; extra addresses get an explanatory unknown record.
smtpTimeout number 10 Per-probe SMTP timeout in seconds (1-60).
maxRetries integer 2 Extra attempts for temporary DNS, connection and SMTP 4xx failures (0-5).
checkSmtp boolean false Run SMTP probing at basic or standard depth.
checkCatchAll boolean false Detect catch-all domains; implies SMTP probing.
includeDomainHealth boolean true Add SPF, DMARC and common DKIM selector findings at standard or deep depth.

emails and emailsText are combined, then duplicates are removed case-insensitively. If both are empty the run fails with a clear message.

Example: clean a pasted list

{
  "emailsText": "alice@company.com, bob@gmial.com\ncarol@mailinator.com",
  "verificationDepth": "standard"
}

Example: deep check for a short list

{
  "emails": ["prospect@target-company.com"],
  "verificationDepth": "deep",
  "checkSmtp": true,
  "checkCatchAll": true,
  "smtpTimeout": 15
}

Example: large list with a per-domain cap

{
  "emailsText": "...",
  "verificationDepth": "basic",
  "maxEmails": 50000,
  "maxEmailsPerDomain": 50,
  "concurrency": 30
}

How to read the verdict

  • valid - syntax and domain routing are sound. If SMTP was enabled and answered, the mailbox was accepted. Without SMTP, this means the domain can receive mail, not that the individual mailbox exists.
  • invalid - broken syntax, a non-existent domain, a null MX record, no MX/A routing, or a definitive mailbox rejection such as SMTP 550.
  • disposable - the domain is on a bundled disposable-domain list.
  • risky - the domain accepts random addresses (catch-all), so an accepted mailbox proves little.
  • unknown - the run could not decide. The reason field says why, and no guess is made in either direction.

SMTP is best-effort, by design

Port 25 is not guaranteed to be open from every network, and many large providers block probe traffic based on the sender IP's reputation, missing reverse DNS or greylisting. When the probe cannot reach the server or the server refuses the probe itself:

  • smtpAttempted stays true.
  • smtpVerifiable is false.
  • checks.smtp is omitted.
  • The verdict is unknown when SMTP was the deciding check.

An unreachable server is never turned into valid or invalid. This is deliberate: a blocked probe says nothing about the mailbox. If you need mailbox-level certainty for a list, run deep from a network with a reputable sending IP and expect some providers to remain unverifiable.

Catch-all detection has the same honest limit: a server that accepts every address at SMTP time may still bounce later. That is why catchAllConfidence is 60 for an accepted random address, and the verdict becomes risky rather than a confident catch-all claim.

Domain health coverage

SPF and DMARC are read from public DNS TXT records. DKIM is checked only on a small set of common selectors (google, selector1, selector2, default, k1, s1, dkim, mail). A missing dkimSelectors field means none of those selectors published a usable key; it does not prove the domain has no DKIM at all, because many domains use custom selectors. Domains with a wildcard TXT record are skipped for DKIM rather than reported as a false match.

Batch behavior and limits

  • Records are pushed in batches while the run is still working, so a long list does not sit entirely in memory.
  • A per-domain semaphore limits how many SMTP connections hit one mail server at once.
  • maxEmails truncates the input and the count is reported in the SUMMARY record and status message.
  • maxEmailsPerDomain does not silently drop addresses: each skipped address gets an unknown record with a reason.
  • No summary row is added to the dataset, so one row still means one unique email address.

FAQ

Is a valid verdict a guarantee that the mailbox exists? No. At standard depth it means the address is well-formed and the domain can receive mail. Only a completed SMTP probe can speak to the mailbox, and even then a provider may accept and bounce later.

Why did a Gmail address come back unknown in deep mode? Gmail and several other large providers reject probe traffic from many IP ranges. The actor reports that honestly instead of calling the address valid or invalid.

Why is checks.smtp missing instead of false? false means the server explicitly rejected the mailbox. A missing value means the probe did not produce a mailbox-level answer (blocked port, policy rejection, timeout or temporary response).

What does catchAllConfidence: 60 mean? The domain accepted a random address. That is evidence of catch-all or accept-then-bounce behavior, but it is not proof that every address is deliverable.

Can I use this for very large lists? Yes. Keep concurrency moderate, set maxEmailsPerDomain to protect individual mail servers, and use basic or standard depth unless mailbox-level probing is required.

Can I use a CSV or an existing dataset? Paste the email column into emailsText, or set datasetId to a dataset that the actor can read (attach it to the run or make it readable by ID) and choose datasetEmailField. Turn on includeSourceFields to carry the original row into sourceData.

What are the remaining limits? Outbound port 25 is unavailable from Apify cloud workers, so deep SMTP probes usually return unknown; 587/465 require authentication and are not a workaround. Provider reputation blocks, catch-all versus accept-then-bounce, catch-all mailbox resolution, arbitrary DKIM selectors, and A/AAAA parking domains cannot be resolved definitively without sending mail. Domain age is not shipped because RDAP has no service for several TLDs. These limits are reported honestly rather than converted into false verdicts.

Where is the batch summary? Open the run's key-value store and read the record named SUMMARY. It contains input counts, truncation, per-domain cap counts, verdict totals and SMTP counters. No extra row is added to the dataset.

Does it need credentials? No. MX, A/AAAA, SPF, DMARC, DKIM and SMTP checks all use public DNS and the recipient's own mail server. No account, API key or cookie is used.

Python quickstart

Use a schema-derived input and the Python standard library to start a run. An Apify account and token are required for execution.

Open the step-by-step Python guide →

Source build 3Q4ZEsXfpGej1x96y. Documentation is regenerated from the default published build; private drafts are not included.