mailintel
Documentation

Email intelligence in one call.

mailintel takes an email address or a domain, resolves the provider from public DNS, and returns the exact IMAP/SMTP connection settings. Zero dependencies, fully typed, and it runs anywhere fetch exists.

$ npm i mailintel
GitHub ↗API reference →

Quickstart

Pass an email address and the username is resolved for you. Pass a bare domain and you get the same connection settings with a username template instead.

quickstart.ts
import { mailintel } from "mailintel";

const result = await mailintel("[email protected]");

if (result.connection) {
  const { imap, smtp, username } = result.connection;

  // imap  -> { host: "imap.gmail.com", port: 993, security: "tls" }
  // smtp  -> { host: "smtp.gmail.com", port: 465, security: "tls" }
  // username -> "[email protected]"
}

It also accepts an array and returns results in the same order:

batch.ts
const results = await mailintel([
  "[email protected]",
  "example.com",
  "[email protected]",
]);

// results[1].connection.username === "%EMAILADDRESS%"
// (no email was supplied, so the template is not resolved)

How it works

Every mail domain publishes MX records that say which servers accept its email. mailintel reads those over DNS-over-HTTPS, matches the host against known provider patterns, and maps the provider to IMAP/SMTP settings.

input
  └── cache
        ├── hit  → return cached result
        └── miss
              ├── domain override?   → high confidence, source: override
              ├── known domain?      → high confidence, source: known-domain
              └── DNS MX lookup
                    ├── MX override? → high confidence, source: mx-lookup
                    ├── known MX?    → high confidence, source: mx-lookup
                    └── ISPDB        → medium confidence
                          └── nothing → connection: null, confidence: low

A known domain costs zero requests. Everything else costs at most two: one DNS query and, if the provider isn't recognized, one Thunderbird ISPDB fetch. Both responses live in a 30-minute in-memory cache.

why not SMTP probing Checking a mailbox by connecting to it is slow, invasive and often blocked. mailintel only reads public routing data, so a lookup is safe to run on every signup.

Options

createMailintel() returns a resolver with your own config, cache and overrides.

factory.ts
import { createMailintel } from "mailintel";

const lookup = createMailintel({
  timeout: 3000,
  cache: true,
  cacheTtl: 30 * 60 * 1000,
  mxLookup: "cloudflare", // or "google", or false
  ispdb: true,
});

await lookup("[email protected]");
lookup.clearCache();
timeoutnumber

Network timeout per request in ms. Default 5000.

cacheboolean

In-memory domain cache. Default true.

cacheTtlnumber

Cache lifetime in ms. Default 30 minutes.

mxLookupboolean | string

true / "cloudflare", "google", or false. Cloudflare is the default.

ispdbboolean

Thunderbird ISPDB fallback. Default true.

fetchtypeof fetch

Custom fetch for proxies, instrumentation or tests.

providersRecord<string, Provider>

Domain-keyed overrides.

mxOverridesRecord<string, Provider>

MX-suffix-keyed overrides.

Result shape

One object for the library, the CLI --json output and the HTTP API.

result.ts
interface MailintelResult {
  email: string;                    // "" for domain-only input
  domain: string;
  provider: string | null;          // "google", "microsoft", "zoho", ...
  providerDisplayName: string | null;
  mx: string | null;                // primary MX host
  type: "free" | "business" | "unknown";
  isFree: boolean;
  hasMx: boolean;
  connection: {
    imap: { host: string; port: number; security: "tls" | "starttls" | "none" };
    smtp: { host: string; port: number; security: "tls" | "starttls" | "none" };
    username: string;
    usernameTemplate: string;
  } | null;
  status: "ok" | "no-mx-records" | "unknown-provider" | "lookup-disabled";
  confidence: "high" | "medium" | "low";
  source: "override" | "known-domain" | "mx-lookup" | null;
  raw: { mx: MxRecord[] | null; dns: object | null; ispdb: string | null };
}

Status & confidence

Never assume a result is usable. Branch on status; treat connection as null until it is ok.

ok

Settings found. connection is populated.

no-mx-records

The domain publishes no MX records and cannot receive mail.

unknown-provider

Mail servers exist, but no IMAP/SMTP mapping for them.

lookup-disabled

Lookups were disabled and the domain is not in the built-in map.

confidence is separate: high means a known domain or MX pattern, medium means ISPDB, low means nothing matched.source tells you which path produced it.

Username templates

Templates follow Thunderbird's ISPDB convention. username is the template with the input applied; usernameTemplate is the raw value.

%EMAILADDRESS%[email protected]

Almost every modern provider

%EMAILLOCALPART%user

Some older and self-hosted setups

%EMAILDOMAIN%example.com

Rare, mostly legacy systems

Custom providers

Private mail infrastructure won't be in any public dataset. Add an override keyed by domain, or by MX suffix when you want every domain on that server covered.

overrides.ts
const lookup = createMailintel({
  providers: {
    "megacorp.io": {
      provider: "internal",
      providerDisplayName: "MegaCorp Mail",
      imap: { host: "imap.megacorp.io", port: 993, security: "tls" },
      smtp: { host: "smtp.megacorp.io", port: 465, security: "tls" },
    },
  },
  mxOverrides: {
    "mail.megacorp.net": { /* same shape */ },
  },
});

await lookup("[email protected]"); // no network call
auth is not included mailintel tells you where to connect, not how to authenticate. Microsoft 365 requires OAuth2 for IMAP/SMTP, and most consumer providers require app passwords. Host and port alone will not log you in.

Browser & edge

The library has no Node dependencies. It uses fetch and nothing else, so it works in the browser, in Cloudflare Workers and in any edge runtime. This site runs the exact same code client-side.

browser.html
<script type="module">
  import { mailintel } from "https://esm.sh/[email protected]";

  const result = await mailintel("[email protected]");
  console.log(result.provider, result.connection?.imap);
</script>
In a browser, keep the default Cloudflare resolver. Cloudflare's DNS-over-HTTPS endpoint sends permissive CORS headers; Google's does not, so mxLookup: "google" belongs on the server. The Thunderbird ISPDB also allows cross-origin requests.

CLI

Same resolver, zero setup. Useful for debugging a domain without writing code.

terminal
$ npx mailintel [email protected]

[email protected]

  provider      google
  domain        acme.dev
  type          business
  confidence    high
  mx            smtp.google.com
  source        mx-lookup

  IMAP  imap.gmail.com:993  SSL
  SMTP  smtp.gmail.com:465  SSL
  Username  [email protected]

$ mailintel acme.dev --json        # machine-readable
$ mailintel [email protected] [email protected]             # multiple inputs
$ mailintel acme.dev --resolver google --no-cache
--json--timeout <ms>--resolver <name>--no-cache--no-ispdb-h-v

Privacy

The library sends only the domain in its DNS and ISPDB requests. This site's playground resolves in your browser; if that path is blocked, the fallback also sends the domain only. Aggregate counters store the domain, provider and outcome — never the local part, never the full address.