├── .editorconfig ├── .github └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .nvmrc ├── build.js ├── example.js ├── index.js ├── license.md ├── package.json ├── readme.md └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | # Use tabs in JavaScript and JSON. 11 | [**.{js, json}] 12 | indent_style = tab 13 | indent_size = 4 14 | 15 | # Use spaces in YAML. 16 | [**.{yml,yaml}] 17 | indent_style = spaces 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: build & publish to npm 2 | 3 | on: 4 | push: 5 | branches: 6 | - publish 7 | schedule: 8 | # on day-of-month 5 at 02:45 9 | - cron: '45 2 5 * *' 10 | 11 | jobs: 12 | test-and-publish: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node-version: ['18'] 17 | 18 | steps: 19 | - name: checkout 20 | uses: actions/checkout@v2 21 | - name: setup Node v${{ matrix.node-version }} 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - run: npm install 26 | 27 | - run: npm run build 28 | - run: ls -lh all.json common.json 29 | - run: npm test 30 | 31 | - name: configure npm auth token 32 | run: | 33 | npm set '//registry.npmjs.org/:_authToken' '${{ secrets.NPM_TOKEN }}' 34 | - name: bump version 35 | run: npm version --no-git-tag-version --force $(npx semver -- -i minor $(npm info email-providers version)) 36 | - name: publish 37 | run: npm publish 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node-version: ['16', '18'] 17 | 18 | steps: 19 | - name: checkout 20 | uses: actions/checkout@v2 21 | - name: setup Node v${{ matrix.node-version }} 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - run: npm install 26 | 27 | - run: npm run build 28 | - run: npm test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | 4 | .nvm-version 5 | node_modules 6 | npm-debug.log 7 | 8 | /package-lock.json 9 | 10 | all.json 11 | common.json 12 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | import _fetch from 'node-fetch' 2 | import uniq from 'lodash.uniq' 3 | import csvParser from 'csv-parser' 4 | import {pipeline, Writable} from 'node:stream' 5 | import {promisify} from 'node:util' 6 | import {writeFile} from 'node:fs' 7 | import createDomainRegex from 'domain-regex' 8 | import {toASCII as punycode} from 'punycode' 9 | 10 | const USER_AGENT = 'https://github.com/derhuerst/email-providers build script' 11 | const PROVIDERS_URL = 'https://raw.githubusercontent.com/derhuerst/emailproviders/master/generate/domains.txt' 12 | const TOP_DOMAINS_URL = 'https://downloads.majestic.com/majestic_million.csv' 13 | 14 | const fetch = (url) => { 15 | return _fetch(url, { 16 | redirect: 'follow', 17 | headers: {'user-agent': USER_AGENT} 18 | }) 19 | .then((res) => { 20 | if (!res.ok) { 21 | const err = new Error(res.statusText) 22 | err.statusCode = res.status 23 | throw err 24 | } 25 | return res 26 | }) 27 | } 28 | 29 | const domainRegex = createDomainRegex() 30 | const fetchProviders = () => { 31 | return fetch(PROVIDERS_URL) 32 | .then(res => res.text()) 33 | .then((data) => { 34 | data = data.split('\n') 35 | .map((d) => d.trim()) 36 | .filter((d) => d.length > 0) 37 | .filter(d => domainRegex.test(punycode(d))) 38 | return uniq(data).sort() 39 | }) 40 | } 41 | 42 | const fetchDomains = () => { 43 | return fetch(TOP_DOMAINS_URL) 44 | .then(res => new Promise((resolve, reject) => { 45 | const domains = new Map() 46 | const onRow = (row, _, cb) => { 47 | const rank = parseInt(row.GlobalRank) 48 | if (rank < 100000) domains.set(row.Domain, rank) 49 | cb() 50 | } 51 | const onRows = (rows, _, cb) => { 52 | for (let i = 0; i < rows.length; i++) { 53 | domains.set(rows[i].Domain, parseInt(rows[i].GlobalRank)) 54 | } 55 | cb() 56 | } 57 | 58 | return pipeline( 59 | res.body, 60 | csvParser(), 61 | new Writable({objectMode: true, write: onRow, writev: onRows}), 62 | (err) => { 63 | if (err) reject(err) 64 | else resolve(domains) 65 | } 66 | ) 67 | })) 68 | } 69 | 70 | const parseDecimalWithComma = decimal => parseInt(decimal.replace(/,/g, ''), 10) 71 | 72 | ;(async () => { 73 | const [all, domains] = await Promise.all([fetchProviders(), fetchDomains()]) 74 | 75 | console.info(`Fetched list of providers. all.json will contain ${all.length}.`) 76 | await promisify(writeFile)('all.json', JSON.stringify(all)) 77 | 78 | const common = all 79 | .filter(provider => domains.has(provider)) 80 | .map(provider => [provider, domains.get(provider)]) 81 | .sort((a, b) => a[1] - b[1]) // by rank, ascending 82 | .map(([provider]) => provider) 83 | 84 | console.info(`common.json will contain the ${common.length} most common.`) 85 | await promisify(writeFile)('common.json', JSON.stringify(common)) 86 | }) 87 | () 88 | .catch((err) => { 89 | console.error(err) 90 | process.exit(1) 91 | }) 92 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module' 4 | const require = createRequire(import.meta.url) 5 | 6 | const all = require('./all.json') 7 | const common = require('./common.json') 8 | 9 | console.log(`from all.json (${all.length}:`) 10 | console.log(all[Math.floor(Math.random() * all.length)]) 11 | 12 | console.log(`from common.json (${common.length}):`) 13 | console.log(common[Math.floor(Math.random() * common.length)]) 14 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module' 4 | const require = createRequire(import.meta.url) 5 | 6 | const all = require('./all.json') 7 | 8 | export default all 9 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023, Jannis R 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "email-providers", 3 | "description": "A list of common eMail providers.", 4 | "version": "2.0.1", 5 | "type": "module", 6 | "main": "index.js", 7 | "files": [ 8 | "index.js", 9 | "all.json", 10 | "common.json" 11 | ], 12 | "keywords": [ 13 | "email", 14 | "mail", 15 | "providers", 16 | "endings", 17 | "address" 18 | ], 19 | "authors": [ 20 | "Jannis R " 21 | ], 22 | "homepage": "https://github.com/derhuerst/email-providers", 23 | "repository": "derhuerst/email-providers", 24 | "bugs": "https://github.com/derhuerst/email-providers/issues", 25 | "license": "ISC", 26 | "engines": { 27 | "node": ">=16" 28 | }, 29 | "devDependencies": { 30 | "node-fetch": "^3.0.0", 31 | "lodash.uniq": "^4.5", 32 | "csv-parser": "^3.0.0", 33 | "domain-regex": "^0.0.1", 34 | "pump": "^3.0.0", 35 | "punycode": "^2" 36 | }, 37 | "scripts": { 38 | "build": "node build.js", 39 | "test": "node test.js", 40 | "prepublishOnly": "npm run build && npm test" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # email-providers 2 | 3 | **A list of common eMail providers.** [Thanks to @goware](https://github.com/goware/emailproviders)! 4 | 5 | [![npm version](https://img.shields.io/npm/v/email-providers.svg)](https://www.npmjs.com/package/email-providers) 6 | ![ISC-licensed](https://img.shields.io/github/license/derhuerst/email-providers.svg) 7 | [![support me via GitHub Sponsors](https://img.shields.io/badge/support%20me-donate-fa7664.svg)](https://github.com/sponsors/derhuerst) 8 | [![chat with me on Twitter](https://img.shields.io/badge/chat%20with%20me-on%20Twitter-1da1f2.svg)](https://twitter.com/derhuerst) 9 | 10 | The npm package (not this Git repo) contains the following data: 11 | 12 | - roughly 8k domains of email providers in `all.json`, and 13 | - roughly 360 with a [Majestic Million rank](https://majestic.com/reports/majestic-million) of `< 100000` in `common.json`. 14 | 15 | 16 | ## Installing 17 | 18 | ```shell 19 | npm install email-providers 20 | ``` 21 | 22 | 23 | ## Usage 24 | 25 | The [package published to npm](https://npmjs.com/email-providers) contains two files `all.json` and `common.json`. The `index.js` entrypoint also exports `all.json`. 26 | 27 | ```js 28 | // use ES Modules with import assertions if your environment already supports them 29 | // https://github.com/tc39/proposal-import-assertions 30 | import all from 'email-providers/all.json' assert {type: 'json'} 31 | import common from 'email-providers/common.json' assert {type: 'json'} 32 | import alsoAll from 'email-providers' 33 | 34 | // alernatively, use module.createRequire 35 | import {createRequire} from 'module' 36 | const require = createRequire(import.meta.url) 37 | 38 | // in a CommonJS environment, you can use require right away, of course 39 | const all = require('email-providers/all.json') 40 | const common = require('email-providers/common.json') 41 | 42 | all.length // 4149 43 | common.length // 312 44 | all[0] // 1033edge.com 45 | common[0] // yahoo.com 46 | ``` 47 | 48 | 49 | ## Contributing 50 | 51 | If you **have a question**, **found a bug** or want to **propose a feature**, have a look at [the issues page](https://github.com/derhuerst/email-providers/issues). 52 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | // todo: use import assertions once they're supported by Node.js & ESLint 2 | // https://github.com/tc39/proposal-import-assertions 3 | import {createRequire} from 'module' 4 | const require = createRequire(import.meta.url) 5 | 6 | import a from 'assert' 7 | import createDomainRegex from 'domain-regex' 8 | import {toASCII as punycode} from 'punycode' 9 | 10 | import entrypoint from './index.js' 11 | const all = require('./all.json') 12 | const common = require('./common.json') 13 | 14 | const domainRegex = createDomainRegex() 15 | const isValidDomain = (domain) => domainRegex.test(punycode(domain)) 16 | 17 | 18 | 19 | a.deepStrictEqual(entrypoint, all, 'module doesn\'t export all') 20 | 21 | 22 | 23 | a(Array.isArray(all), 'not an array') 24 | a(all.length > 0, 'no providers in all.json') 25 | 26 | for (let provider of all) 27 | a(isValidDomain(provider), `invalid domain: ${provider}`) 28 | 29 | 30 | 31 | a(Array.isArray(common), 'not an array') 32 | a(common.length > 0, 'no providers in common.json') 33 | 34 | for (let provider of common) 35 | a(isValidDomain(provider), `invalid domain: ${provider}`) 36 | --------------------------------------------------------------------------------