# Changelog of Email Verifier & Validator — Bulk Email List Cleaner, $0.75/1k (`yabanana99/email-list-cleaner-verifier`) Actor

- **URL**: https://apify.com/yabanana99/email-list-cleaner-verifier/changelog.md
- **Full Actor documentation**: https://apify.com/yabanana99/email-list-cleaner-verifier.md

## Changelog

### 0.2

- **Email column detection is validated against the data.** A column matched by
  header name must actually contain addresses (≥30% of sampled values); if it
  does not, the column where most values do is used instead, and if no column
  qualifies nothing is read and nothing is charged. Ambiguous names (`to`,
  `mail`, `recipient`) now rank below unambiguous ones, and the column finally
  used is always reported in the log and in the run report.
- **Unbalanced quotes no longer eat a CSV in silence:** the addresses extracted
  are compared with the addresses visible in the raw text, and the file is
  re-parsed ignoring quotes only when addresses were actually lost. The
  alternative parse wins only if it yields more real addresses, never more rows,
  so a valid CSV with multi-line quoted fields (notes, postal addresses) is
  never shredded — it gets an informational note instead of an alarm.
- **Role accounts no longer lose points.** `ceo@`, `hello@`, `press@` keep their
  score and stay in `clean` (new `role_segment` option to move them), a new
  `role.csv` export lists them all, and the suffix heuristic no longer flags
  real people (`it.max@`, `pr.ben@`).
- **Addresses buried in free text are extracted, not rejected.** A cell such as
  `write to p7@ex.com for info` yields a clean, verified contact instead of an
  invalid row: the address is pulled out with the same pattern that selected the
  column, `email_original` keeps the original cell and the record is marked with
  `note:address_extracted_from_surrounding_text`. Between several addresses in
  one cell the first non-role one wins (`no-reply@sys.com on behalf of
  mario@acme.com` → `mario@acme.com`), and punctuation glued to the address is
  trimmed with the IANA TLD list (`p1@ex.com.Grazie` → `p1@ex.com` instead of a
  dead `ex.com.grazie`); an unknown TLD is always left untouched.
- A column named explicitly in `email_column` is now read even when only a few of
  its values look like an address — that is how a free-text notes column is
  harvested. Auto-detection stays strict, and its "no email column" message now
  points at that option.
- The unbalanced-quote warning is emitted only when the re-parse actually
  recovered addresses, so it can no longer mask the real diagnosis on a file
  whose quoting is perfect.
- SSRF: carrier-grade NAT (`100.64.0.0/10`), IETF protocol assignments and
  benchmarking ranges are blocked too, and IPv4-mapped IPv6 addresses are
  unwrapped before the check.
- **Circuit breaker on the failure signature, not on the failure rate:** the run
  stops only when the failure looks like a parsing accident — values with no `@`
  anywhere, or prose that contains no address at all. Truncated addresses
  (`john.smith@`) are the signature of a rotten list, which is the job, and are
  processed to the last row.
- **New pricing model:** `email-verified` ($0.00075) is charged only when a DNS
  or SMTP lookup was actually performed; addresses decided locally (broken
  syntax, disposable list, `check_mx: false`) are charged as `email-screened`
  ($0.0002). `actor-start` is charged once one input file has been downloaded
  (that work is real compute), and not at all when nothing could be downloaded.
- **Disposable list grown to 75,000+ domains** (merged from two maintained MIT
  feeds) plus a `disposable_list_url` option for your own feed.
- Likely typo domains (`gmial.com`) are now capped into the `risky` segment
  instead of being reported as clean.
- Wide CRM exports (up to 1,024 columns) are read to the end: files are sampled
  for detection and then streamed, so only one column is retained.
- Content beats file name: images/PDFs renamed to `.csv` are refused, UTF-16 and
  UTF-32 files are decoded properly, spreadsheet formulas with no cached value
  are reported, and non-contact sheets in a workbook are skipped, not merged.
- Empty or unreadable inputs end the run as `SUCCEEDED` with a status message
  instead of `FAILED`; a failed charge is no longer reported as revenue; the
  final `_summary` record carries the report as a JSON string so a dataset CSV
  export gains one column instead of dozens.
- Base image pinned by digest.

### 0.1

- First release: full list-hygiene pipeline — syntax (RFC-ish) → normalization
  → deduplication → classification (disposable / role / free provider) → MX
  verification → optional SMTP probe → 0-100 score with an explicit confidence
  level and a clean / risky / invalid segment.
- Input from CSV, TSV, XLSX, JSON, JSON Lines and plain-text lists (URL, base64
  or key-value-store record) plus inline addresses; automatic email-column
  detection; up to 20 files and 100000 rows per run merged and deduplicated
  together.
- Provider-aware normalization: Gmail dots, googlemail.com alias, `+tag`
  removal restricted to providers that document sub-addressing, IDN/punycode,
  display names and `mailto:` prefixes stripped.
- Deduplication reports `occurrences`, `duplicate_of` and whether the duplicate
  was exact or only visible after normalization. Duplicates are never charged
  twice.
- MX lookups are cached per domain, run with bounded concurrency and a
  per-lookup timeout, and are limited by a global DNS time budget; a timeout is
  reported as an inconclusive status with low confidence, never as a verdict.
- SMTP probing is opt-in, sequential, budgeted, with catch-all detection and an
  automatic stop when outbound port 25 is unavailable.
- Segment CSV exports (`clean.csv`, `risky.csv`, `invalid.csv`) and a
  `HYGIENE_REPORT` in the key-value store, plus a final `_summary` dataset
  record with the whole hygiene report.
- Advisory "did you mean" suggestions for domains one or two edits away from a
  major provider (gmial.com → gmail.com).
