@mikkelscheike/email-provider-links 2.8.0 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -43
- package/dist/alias-detection.js +42 -213
- package/dist/api.d.ts +28 -12
- package/dist/api.js +26 -26
- package/dist/concurrent-dns.js +6 -5
- package/dist/hash-verifier.js +2 -2
- package/dist/loader.js +10 -3
- package/dist/schema.d.ts +22 -3
- package/dist/url-validator.js +22 -103
- package/package.json +4 -3
- package/providers/emailproviders.json +196 -39
package/README.md
CHANGED
|
@@ -92,51 +92,53 @@ Fully compatible with the latest Node.js 24.x! The library is tested on:
|
|
|
92
92
|
|
|
93
93
|
## API Reference
|
|
94
94
|
|
|
95
|
-
###
|
|
96
|
-
|
|
95
|
+
### Core Functions
|
|
96
|
+
|
|
97
|
+
#### `getEmailProvider(email, timeout?)`
|
|
98
|
+
**Recommended** - Complete provider detection with business domain support.
|
|
97
99
|
|
|
98
100
|
```typescript
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// 🔍 For business domains - DNS lookup required, timeout matters
|
|
107
|
-
const biz1 = await getEmailProvider('user@mycompany.com'); // 5000ms timeout (default)
|
|
108
|
-
const biz2 = await getEmailProvider('user@mycompany.com', 2000); // 2000ms timeout (faster fail)
|
|
109
|
-
const biz3 = await getEmailProvider('user@mycompany.com', 10000); // 10000ms timeout (slower networks)
|
|
110
|
-
// All may detect: { provider: "Google Workspace", detectionMethod: "mx_record" }
|
|
111
|
-
|
|
112
|
-
// 🎯 WHY USE CUSTOM TIMEOUT?
|
|
113
|
-
// - Faster apps: Use 2000ms to fail fast on unknown domains
|
|
114
|
-
// - Slower networks: Use 10000ms to avoid premature timeouts
|
|
115
|
-
// - Enterprise: Use 1000ms for strict SLA requirements
|
|
101
|
+
// Known providers (instant response)
|
|
102
|
+
const result1 = await getEmailProvider('user@gmail.com');
|
|
103
|
+
// Returns: { provider: "Gmail", loginUrl: "https://mail.google.com/mail/" }
|
|
104
|
+
|
|
105
|
+
// Business domains (DNS lookup with timeout)
|
|
106
|
+
const result2 = await getEmailProvider('user@company.com', 2000);
|
|
107
|
+
// Returns: { provider: "Google Workspace", detectionMethod: "mx_record" }
|
|
116
108
|
```
|
|
117
109
|
|
|
118
|
-
|
|
119
|
-
**
|
|
110
|
+
#### `getEmailProviderSync(email)`
|
|
111
|
+
**Fast** - Instant checks for known providers (no DNS lookup).
|
|
120
112
|
|
|
121
113
|
```typescript
|
|
122
|
-
const result = getEmailProviderSync('user@
|
|
123
|
-
// Returns: { provider, loginUrl
|
|
114
|
+
const result = getEmailProviderSync('user@outlook.com');
|
|
115
|
+
// Returns: { provider: "Outlook", loginUrl: "https://outlook.live.com/" }
|
|
124
116
|
```
|
|
125
117
|
|
|
126
|
-
###
|
|
127
|
-
|
|
118
|
+
### Email Alias Support
|
|
119
|
+
|
|
120
|
+
The library handles provider-specific email alias rules:
|
|
128
121
|
|
|
129
122
|
```typescript
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
123
|
+
// Gmail ignores dots and plus addressing
|
|
124
|
+
emailsMatch('user.name+work@gmail.com', 'username@gmail.com') // true
|
|
125
|
+
|
|
126
|
+
// Outlook preserves dots but ignores plus addressing
|
|
127
|
+
emailsMatch('user.name+work@outlook.com', 'username@outlook.com') // false
|
|
128
|
+
|
|
129
|
+
// Normalize emails to canonical form
|
|
130
|
+
const canonical = normalizeEmail('u.s.e.r+tag@gmail.com');
|
|
131
|
+
console.log(canonical); // 'user@gmail.com'
|
|
138
132
|
```
|
|
139
133
|
|
|
134
|
+
**Provider Rules Overview**:
|
|
135
|
+
- **Gmail**: Ignores dots, supports plus addressing
|
|
136
|
+
- **Outlook**: Preserves dots, supports plus addressing
|
|
137
|
+
- **Yahoo**: Preserves dots, supports plus addressing
|
|
138
|
+
- **ProtonMail**: Preserves dots, supports plus addressing
|
|
139
|
+
- **FastMail**: Preserves dots, supports plus addressing
|
|
140
|
+
- **AOL**: Preserves everything except case
|
|
141
|
+
|
|
140
142
|
## Real-World Example
|
|
141
143
|
|
|
142
144
|
```typescript
|
|
@@ -256,17 +258,32 @@ The library implements careful memory management:
|
|
|
256
258
|
|
|
257
259
|
### Performance Benchmarks
|
|
258
260
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
-
|
|
263
|
-
-
|
|
264
|
-
-
|
|
265
|
-
-
|
|
266
|
-
|
|
267
|
-
|
|
261
|
+
Extensively optimized for both speed and memory efficiency:
|
|
262
|
+
|
|
263
|
+
**Speed Metrics**:
|
|
264
|
+
- Initial provider load: ~0.5ms
|
|
265
|
+
- Known provider lookup: <1ms
|
|
266
|
+
- DNS-based detection: ~27ms average
|
|
267
|
+
- Batch processing: 1000 operations in ~1.1ms
|
|
268
|
+
- Email validation: <1ms for complex IDN domains
|
|
269
|
+
|
|
270
|
+
**Memory Usage**:
|
|
271
|
+
- Initial footprint: ~0.08MB
|
|
272
|
+
- Per operation: ~0.03MB per 1000 lookups
|
|
273
|
+
- Peak usage: <25MB under heavy load
|
|
274
|
+
- Cache efficiency: >99% hit rate
|
|
275
|
+
- Garbage collection: Automatic optimization
|
|
276
|
+
|
|
277
|
+
**Real-World Performance**:
|
|
278
|
+
- 50,000+ operations/second for known providers
|
|
279
|
+
- 100 concurrent DNS lookups in <1 second
|
|
280
|
+
- Average latency: <1ms for cached lookups
|
|
281
|
+
- Maximum latency: <5ms per lookup
|
|
282
|
+
|
|
283
|
+
To run benchmarks:
|
|
268
284
|
```bash
|
|
269
|
-
npm run benchmark
|
|
285
|
+
npm run benchmark # Basic benchmarks
|
|
286
|
+
node --expose-gc benchmark/memory.ts # Detailed memory analysis
|
|
270
287
|
```
|
|
271
288
|
|
|
272
289
|
## Contributing
|
package/dist/alias-detection.js
CHANGED
|
@@ -9,184 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.detectEmailAlias = detectEmailAlias;
|
|
10
10
|
exports.normalizeEmail = normalizeEmail;
|
|
11
11
|
exports.emailsMatch = emailsMatch;
|
|
12
|
-
|
|
13
|
-
* Email alias rules for major providers
|
|
14
|
-
*/
|
|
15
|
-
const ALIAS_RULES = [
|
|
16
|
-
{
|
|
17
|
-
domains: ['gmail.com', 'googlemail.com'],
|
|
18
|
-
supportsPlusAddressing: true,
|
|
19
|
-
ignoresDots: true,
|
|
20
|
-
normalize: (email) => {
|
|
21
|
-
const parts = email.toLowerCase().split('@');
|
|
22
|
-
const username = parts[0];
|
|
23
|
-
const domain = parts[1];
|
|
24
|
-
if (!username || !domain) {
|
|
25
|
-
return email.toLowerCase();
|
|
26
|
-
}
|
|
27
|
-
// Remove dots and everything after +
|
|
28
|
-
const cleanUsername = username.replace(/\./g, '').split('+')[0];
|
|
29
|
-
return `${cleanUsername}@${domain}`;
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
domains: ['outlook.com', 'hotmail.com', 'live.com', 'msn.com', 'hotmail.co.uk', 'hotmail.fr', 'hotmail.it', 'hotmail.es', 'hotmail.de', 'live.co.uk', 'live.fr', 'live.it', 'live.nl', 'live.com.au', 'live.ca', 'live.jp'],
|
|
34
|
-
supportsPlusAddressing: true,
|
|
35
|
-
ignoresDots: false,
|
|
36
|
-
normalize: (email) => {
|
|
37
|
-
const parts = email.toLowerCase().split('@');
|
|
38
|
-
const username = parts[0];
|
|
39
|
-
const domain = parts[1];
|
|
40
|
-
if (!username || !domain) {
|
|
41
|
-
return email.toLowerCase();
|
|
42
|
-
}
|
|
43
|
-
// Only remove plus addressing for Outlook
|
|
44
|
-
const cleanUsername = username.split('+')[0];
|
|
45
|
-
return `${cleanUsername}@${domain}`;
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
domains: ['yahoo.com', 'yahoo.co.uk', 'yahoo.fr', 'yahoo.co.in', 'yahoo.com.br', 'yahoo.co.jp', 'yahoo.it', 'yahoo.de', 'yahoo.in', 'yahoo.es', 'yahoo.ca', 'yahoo.com.au', 'yahoo.com.ar', 'yahoo.com.mx', 'yahoo.co.id', 'yahoo.com.sg', 'ymail.com', 'rocketmail.com'],
|
|
50
|
-
supportsPlusAddressing: true,
|
|
51
|
-
ignoresDots: false,
|
|
52
|
-
normalize: (email) => {
|
|
53
|
-
const parts = email.toLowerCase().split('@');
|
|
54
|
-
const username = parts[0];
|
|
55
|
-
const domain = parts[1];
|
|
56
|
-
if (!username || !domain) {
|
|
57
|
-
return email.toLowerCase();
|
|
58
|
-
}
|
|
59
|
-
const cleanUsername = username.split('+')[0];
|
|
60
|
-
return `${cleanUsername}@${domain}`;
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
domains: ['fastmail.com', 'fastmail.fm'],
|
|
65
|
-
supportsPlusAddressing: true,
|
|
66
|
-
ignoresDots: false,
|
|
67
|
-
normalize: (email) => {
|
|
68
|
-
const parts = email.toLowerCase().split('@');
|
|
69
|
-
const username = parts[0];
|
|
70
|
-
const domain = parts[1];
|
|
71
|
-
if (!username || !domain) {
|
|
72
|
-
return email.toLowerCase();
|
|
73
|
-
}
|
|
74
|
-
const cleanUsername = username.split('+')[0];
|
|
75
|
-
return `${cleanUsername}@${domain}`;
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
domains: ['proton.me', 'protonmail.com', 'protonmail.ch', 'pm.me'],
|
|
80
|
-
supportsPlusAddressing: true,
|
|
81
|
-
ignoresDots: false,
|
|
82
|
-
normalize: (email) => {
|
|
83
|
-
const parts = email.toLowerCase().split('@');
|
|
84
|
-
const username = parts[0];
|
|
85
|
-
const domain = parts[1];
|
|
86
|
-
if (!username || !domain) {
|
|
87
|
-
return email.toLowerCase();
|
|
88
|
-
}
|
|
89
|
-
const cleanUsername = username.split('+')[0];
|
|
90
|
-
return `${cleanUsername}@${domain}`;
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
domains: ['tutanota.com', 'tutanota.de', 'tutamail.com', 'tuta.io', 'keemail.me', 'tuta.com'],
|
|
95
|
-
supportsPlusAddressing: true,
|
|
96
|
-
ignoresDots: false,
|
|
97
|
-
normalize: (email) => {
|
|
98
|
-
const parts = email.toLowerCase().split('@');
|
|
99
|
-
const username = parts[0];
|
|
100
|
-
const domain = parts[1];
|
|
101
|
-
if (!username || !domain) {
|
|
102
|
-
return email.toLowerCase();
|
|
103
|
-
}
|
|
104
|
-
const cleanUsername = username.split('+')[0];
|
|
105
|
-
return `${cleanUsername}@${domain}`;
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
domains: ['zoho.com', 'zohomail.com', 'zoho.eu'],
|
|
110
|
-
supportsPlusAddressing: true,
|
|
111
|
-
ignoresDots: false,
|
|
112
|
-
normalize: (email) => {
|
|
113
|
-
const parts = email.toLowerCase().split('@');
|
|
114
|
-
const username = parts[0];
|
|
115
|
-
const domain = parts[1];
|
|
116
|
-
if (!username || !domain) {
|
|
117
|
-
return email.toLowerCase();
|
|
118
|
-
}
|
|
119
|
-
const cleanUsername = username.split('+')[0];
|
|
120
|
-
return `${cleanUsername}@${domain}`;
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
domains: ['icloud.com', 'me.com', 'mac.com'],
|
|
125
|
-
supportsPlusAddressing: true,
|
|
126
|
-
ignoresDots: false,
|
|
127
|
-
normalize: (email) => {
|
|
128
|
-
const parts = email.toLowerCase().split('@');
|
|
129
|
-
const username = parts[0];
|
|
130
|
-
const domain = parts[1];
|
|
131
|
-
if (!username || !domain) {
|
|
132
|
-
return email.toLowerCase();
|
|
133
|
-
}
|
|
134
|
-
const cleanUsername = username.split('+')[0];
|
|
135
|
-
return `${cleanUsername}@${domain}`;
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
domains: ['mail.com'],
|
|
140
|
-
supportsPlusAddressing: true,
|
|
141
|
-
ignoresDots: false,
|
|
142
|
-
normalize: (email) => {
|
|
143
|
-
const parts = email.toLowerCase().split('@');
|
|
144
|
-
const username = parts[0];
|
|
145
|
-
const domain = parts[1];
|
|
146
|
-
if (!username || !domain) {
|
|
147
|
-
return email.toLowerCase();
|
|
148
|
-
}
|
|
149
|
-
const cleanUsername = username.split('+')[0];
|
|
150
|
-
return `${cleanUsername}@${domain}`;
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
domains: ['aol.com', 'love.com', 'ygm.com', 'games.com', 'wow.com', 'aim.com'],
|
|
155
|
-
supportsPlusAddressing: false,
|
|
156
|
-
ignoresDots: false,
|
|
157
|
-
normalize: (email) => email.toLowerCase()
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
domains: ['mail.ru'],
|
|
161
|
-
supportsPlusAddressing: true,
|
|
162
|
-
ignoresDots: false,
|
|
163
|
-
normalize: (email) => {
|
|
164
|
-
const parts = email.toLowerCase().split('@');
|
|
165
|
-
const username = parts[0];
|
|
166
|
-
const domain = parts[1];
|
|
167
|
-
if (!username || !domain) {
|
|
168
|
-
return email.toLowerCase();
|
|
169
|
-
}
|
|
170
|
-
const cleanUsername = username.split('+')[0];
|
|
171
|
-
return `${cleanUsername}@${domain}`;
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
domains: ['yandex.com', 'yandex.ru'],
|
|
176
|
-
supportsPlusAddressing: true,
|
|
177
|
-
ignoresDots: false,
|
|
178
|
-
normalize: (email) => {
|
|
179
|
-
const parts = email.toLowerCase().split('@');
|
|
180
|
-
const username = parts[0];
|
|
181
|
-
const domain = parts[1];
|
|
182
|
-
if (!username || !domain) {
|
|
183
|
-
return email.toLowerCase();
|
|
184
|
-
}
|
|
185
|
-
const cleanUsername = username.split('+')[0];
|
|
186
|
-
return `${cleanUsername}@${domain}`;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
];
|
|
12
|
+
const loader_1 = require("./loader");
|
|
190
13
|
/**
|
|
191
14
|
* Validates email format
|
|
192
15
|
*/
|
|
@@ -194,12 +17,6 @@ function isValidEmail(email) {
|
|
|
194
17
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
195
18
|
return emailRegex.test(email);
|
|
196
19
|
}
|
|
197
|
-
/**
|
|
198
|
-
* Gets the alias rule for a given domain
|
|
199
|
-
*/
|
|
200
|
-
function getAliasRule(domain) {
|
|
201
|
-
return ALIAS_RULES.find(rule => rule.domains.includes(domain.toLowerCase())) || null;
|
|
202
|
-
}
|
|
203
20
|
/**
|
|
204
21
|
* Detects and analyzes email aliases
|
|
205
22
|
*
|
|
@@ -217,49 +34,61 @@ function detectEmailAlias(email) {
|
|
|
217
34
|
if (!username || !domain) {
|
|
218
35
|
throw new Error('Invalid email format - missing username or domain');
|
|
219
36
|
}
|
|
220
|
-
const
|
|
37
|
+
const { domainMap } = (0, loader_1.loadProviders)();
|
|
38
|
+
const provider = domainMap.get(domain);
|
|
221
39
|
const result = {
|
|
222
40
|
canonical: originalEmail.toLowerCase(),
|
|
223
41
|
original: originalEmail,
|
|
224
42
|
isAlias: false,
|
|
225
43
|
aliasType: 'none'
|
|
226
44
|
};
|
|
227
|
-
if (!
|
|
228
|
-
// No specific rule, just normalize case
|
|
229
|
-
result.canonical = originalEmail.toLowerCase();
|
|
45
|
+
if (!provider?.alias) {
|
|
230
46
|
return result;
|
|
231
47
|
}
|
|
232
48
|
result.provider = domain;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return result;
|
|
49
|
+
let normalizedUsername = username;
|
|
50
|
+
let isAlias = false;
|
|
51
|
+
let aliasType = 'none';
|
|
52
|
+
let aliasPart;
|
|
53
|
+
// Handle case sensitivity (all modern providers are case-insensitive)
|
|
54
|
+
if (provider.alias?.case?.ignore) {
|
|
55
|
+
if (provider.alias.case?.strip) {
|
|
56
|
+
normalizedUsername = normalizedUsername.toLowerCase();
|
|
57
|
+
}
|
|
243
58
|
}
|
|
244
|
-
//
|
|
245
|
-
if (
|
|
246
|
-
const
|
|
247
|
-
if (
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
59
|
+
// Handle plus addressing (common for Gmail, Outlook, Yahoo, etc.)
|
|
60
|
+
if (provider.alias?.plus?.ignore) {
|
|
61
|
+
const plusIndex = username.indexOf('+');
|
|
62
|
+
if (plusIndex !== -1) {
|
|
63
|
+
aliasPart = username.substring(plusIndex + 1);
|
|
64
|
+
isAlias = true;
|
|
65
|
+
aliasType = 'plus';
|
|
66
|
+
if (provider.alias.plus?.strip) {
|
|
67
|
+
normalizedUsername = username.slice(0, plusIndex);
|
|
68
|
+
}
|
|
253
69
|
}
|
|
254
70
|
}
|
|
255
|
-
//
|
|
256
|
-
if (
|
|
257
|
-
const
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
|
|
71
|
+
// Handle dots (primarily for Gmail)
|
|
72
|
+
if (provider.alias?.dots?.ignore) {
|
|
73
|
+
const hasDots = username.includes('.');
|
|
74
|
+
if (hasDots) {
|
|
75
|
+
if (!isAlias) {
|
|
76
|
+
aliasPart = username;
|
|
77
|
+
isAlias = true;
|
|
78
|
+
aliasType = 'dot';
|
|
79
|
+
}
|
|
80
|
+
if (provider.alias.dots?.strip) {
|
|
81
|
+
normalizedUsername = normalizedUsername.replace(/\./g, '');
|
|
82
|
+
}
|
|
261
83
|
}
|
|
262
84
|
}
|
|
85
|
+
// Build the canonical form
|
|
86
|
+
result.canonical = `${normalizedUsername}@${domain}`;
|
|
87
|
+
result.isAlias = isAlias;
|
|
88
|
+
result.aliasType = aliasType;
|
|
89
|
+
if (aliasPart !== undefined) {
|
|
90
|
+
result.aliasPart = aliasPart;
|
|
91
|
+
}
|
|
263
92
|
return result;
|
|
264
93
|
}
|
|
265
94
|
/**
|
package/dist/api.d.ts
CHANGED
|
@@ -4,10 +4,26 @@
|
|
|
4
4
|
* Simplified API with better error handling and performance improvements.
|
|
5
5
|
* Clean function names and enhanced error context.
|
|
6
6
|
*/
|
|
7
|
+
export type ProviderType = 'public_provider' | 'custom_provider' | 'proxy_service';
|
|
7
8
|
export interface EmailProvider {
|
|
8
9
|
companyProvider: string;
|
|
9
10
|
loginUrl: string | null;
|
|
10
11
|
domains: string[];
|
|
12
|
+
type: ProviderType;
|
|
13
|
+
alias?: {
|
|
14
|
+
dots?: {
|
|
15
|
+
ignore: boolean;
|
|
16
|
+
strip: boolean;
|
|
17
|
+
};
|
|
18
|
+
plus?: {
|
|
19
|
+
ignore: boolean;
|
|
20
|
+
strip: boolean;
|
|
21
|
+
};
|
|
22
|
+
case?: {
|
|
23
|
+
ignore: boolean;
|
|
24
|
+
strip: boolean;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
11
27
|
customDomainDetection?: {
|
|
12
28
|
mxPatterns?: string[];
|
|
13
29
|
txtPatterns?: string[];
|
|
@@ -50,14 +66,14 @@ export interface EmailProviderResult {
|
|
|
50
66
|
* @example
|
|
51
67
|
* ```typescript
|
|
52
68
|
* // Consumer email
|
|
53
|
-
* const
|
|
54
|
-
* console.log(
|
|
55
|
-
* console.log(
|
|
69
|
+
* const result = await getEmailProvider('local@domain.tld');
|
|
70
|
+
* console.log(result.provider?.companyProvider); // Provider name
|
|
71
|
+
* console.log(result.loginUrl); // Login URL
|
|
56
72
|
*
|
|
57
73
|
* // Business domain
|
|
58
|
-
* const business = await getEmailProvider('
|
|
59
|
-
* console.log(business.provider?.companyProvider); //
|
|
60
|
-
* console.log(business.detectionMethod); //
|
|
74
|
+
* const business = await getEmailProvider('local@business.tld');
|
|
75
|
+
* console.log(business.provider?.companyProvider); // Detected provider
|
|
76
|
+
* console.log(business.detectionMethod); // Detection method
|
|
61
77
|
*
|
|
62
78
|
* // Error handling
|
|
63
79
|
* const invalid = await getEmailProvider('invalid-email');
|
|
@@ -100,11 +116,11 @@ export declare function getEmailProviderSync(email: string): EmailProviderResult
|
|
|
100
116
|
*
|
|
101
117
|
* @example
|
|
102
118
|
* ```typescript
|
|
103
|
-
* const canonical = normalizeEmail('
|
|
104
|
-
* console.log(canonical); // '
|
|
119
|
+
* const canonical = normalizeEmail('L.O.C.A.L+work@DOMAIN.TLD');
|
|
120
|
+
* console.log(canonical); // 'local@domain.tld'
|
|
105
121
|
*
|
|
106
|
-
* const
|
|
107
|
-
* console.log(
|
|
122
|
+
* const provider = normalizeEmail('local+newsletter@provider.tld');
|
|
123
|
+
* console.log(provider); // 'local@provider.tld'
|
|
108
124
|
* ```
|
|
109
125
|
*/
|
|
110
126
|
export declare function normalizeEmail(email: string): string;
|
|
@@ -120,10 +136,10 @@ export declare function normalizeEmail(email: string): string;
|
|
|
120
136
|
*
|
|
121
137
|
* @example
|
|
122
138
|
* ```typescript
|
|
123
|
-
* const match = emailsMatch('
|
|
139
|
+
* const match = emailsMatch('local@domain.tld', 'l.o.c.a.l+work@domain.tld');
|
|
124
140
|
* console.log(match); // true
|
|
125
141
|
*
|
|
126
|
-
* const different = emailsMatch('
|
|
142
|
+
* const different = emailsMatch('local@domain.tld', 'other@domain.tld');
|
|
127
143
|
* console.log(different); // false
|
|
128
144
|
* ```
|
|
129
145
|
*/
|
package/dist/api.js
CHANGED
|
@@ -29,14 +29,14 @@ const loader_1 = require("./loader");
|
|
|
29
29
|
* @example
|
|
30
30
|
* ```typescript
|
|
31
31
|
* // Consumer email
|
|
32
|
-
* const
|
|
33
|
-
* console.log(
|
|
34
|
-
* console.log(
|
|
32
|
+
* const result = await getEmailProvider('local@domain.tld');
|
|
33
|
+
* console.log(result.provider?.companyProvider); // Provider name
|
|
34
|
+
* console.log(result.loginUrl); // Login URL
|
|
35
35
|
*
|
|
36
36
|
* // Business domain
|
|
37
|
-
* const business = await getEmailProvider('
|
|
38
|
-
* console.log(business.provider?.companyProvider); //
|
|
39
|
-
* console.log(business.detectionMethod); //
|
|
37
|
+
* const business = await getEmailProvider('local@business.tld');
|
|
38
|
+
* console.log(business.provider?.companyProvider); // Detected provider
|
|
39
|
+
* console.log(business.detectionMethod); // Detection method
|
|
40
40
|
*
|
|
41
41
|
* // Error handling
|
|
42
42
|
* const invalid = await getEmailProvider('invalid-email');
|
|
@@ -257,11 +257,11 @@ function getEmailProviderSync(email) {
|
|
|
257
257
|
*
|
|
258
258
|
* @example
|
|
259
259
|
* ```typescript
|
|
260
|
-
* const canonical = normalizeEmail('
|
|
261
|
-
* console.log(canonical); // '
|
|
260
|
+
* const canonical = normalizeEmail('L.O.C.A.L+work@DOMAIN.TLD');
|
|
261
|
+
* console.log(canonical); // 'local@domain.tld'
|
|
262
262
|
*
|
|
263
|
-
* const
|
|
264
|
-
* console.log(
|
|
263
|
+
* const provider = normalizeEmail('local+newsletter@provider.tld');
|
|
264
|
+
* console.log(provider); // 'local@provider.tld'
|
|
265
265
|
* ```
|
|
266
266
|
*/
|
|
267
267
|
function normalizeEmail(email) {
|
|
@@ -277,21 +277,21 @@ function normalizeEmail(email) {
|
|
|
277
277
|
}
|
|
278
278
|
let localPart = lowercaseEmail.slice(0, atIndex);
|
|
279
279
|
const domainPart = lowercaseEmail.slice(atIndex + 1);
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
localPart = localPart.
|
|
280
|
+
// Use cached providers for domain lookup
|
|
281
|
+
const { domainMap } = (0, loader_1.loadProviders)();
|
|
282
|
+
const provider = domainMap.get(domainPart);
|
|
283
|
+
if (provider?.alias) {
|
|
284
|
+
// Provider supports aliasing
|
|
285
|
+
if (provider.alias.dots) {
|
|
286
|
+
// Remove all dots from local part (e.g. Gmail)
|
|
287
|
+
localPart = localPart.replace(/\./g, '');
|
|
288
288
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
289
|
+
if (provider.alias.plus) {
|
|
290
|
+
// Remove plus addressing (everything after +)
|
|
291
|
+
const plusIndex = localPart.indexOf('+');
|
|
292
|
+
if (plusIndex !== -1) {
|
|
293
|
+
localPart = localPart.slice(0, plusIndex);
|
|
294
|
+
}
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
return `${localPart}@${domainPart}`;
|
|
@@ -308,10 +308,10 @@ function normalizeEmail(email) {
|
|
|
308
308
|
*
|
|
309
309
|
* @example
|
|
310
310
|
* ```typescript
|
|
311
|
-
* const match = emailsMatch('
|
|
311
|
+
* const match = emailsMatch('local@domain.tld', 'l.o.c.a.l+work@domain.tld');
|
|
312
312
|
* console.log(match); // true
|
|
313
313
|
*
|
|
314
|
-
* const different = emailsMatch('
|
|
314
|
+
* const different = emailsMatch('local@domain.tld', 'other@domain.tld');
|
|
315
315
|
* console.log(different); // false
|
|
316
316
|
* ```
|
|
317
317
|
*/
|
package/dist/concurrent-dns.js
CHANGED
|
@@ -367,11 +367,12 @@ class ConcurrentDNSDetector {
|
|
|
367
367
|
const mxQuery = queries.find(q => q.type === 'mx' && q.success);
|
|
368
368
|
if (!mxQuery?.records)
|
|
369
369
|
return null;
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
370
|
+
// Get proxy services from provider data
|
|
371
|
+
const proxyProviders = this.providers.filter(p => p.type === 'proxy_service');
|
|
372
|
+
const proxyPatterns = proxyProviders.map(provider => ({
|
|
373
|
+
service: provider.companyProvider,
|
|
374
|
+
patterns: [...(provider.customDomainDetection?.mxPatterns || []), ...(provider.domains || [])].map(p => p.toLowerCase())
|
|
375
|
+
}));
|
|
375
376
|
for (const record of mxQuery.records) {
|
|
376
377
|
const exchange = record.exchange?.toLowerCase() || '';
|
|
377
378
|
for (const proxy of proxyPatterns) {
|
package/dist/hash-verifier.js
CHANGED
|
@@ -27,9 +27,9 @@ const path_1 = require("path");
|
|
|
27
27
|
*/
|
|
28
28
|
const KNOWN_GOOD_HASHES = {
|
|
29
29
|
// SHA-256 hash of the legitimate emailproviders.json
|
|
30
|
-
'emailproviders.json': '
|
|
30
|
+
'emailproviders.json': 'c9c3cb1590820989071ec2bea8a7560496188031f8fa6367153e642315824cdb',
|
|
31
31
|
// You can add hashes for other critical files
|
|
32
|
-
'package.json': '
|
|
32
|
+
'package.json': 'da08eadfe33e8a5c5bcc3db0f0dccc402b4d8ab8440ff57d2e9aa986921ac66d'
|
|
33
33
|
};
|
|
34
34
|
/**
|
|
35
35
|
* Calculates SHA-256 hash of a file or string content
|
package/dist/loader.js
CHANGED
|
@@ -29,13 +29,20 @@ const DEFAULT_CONFIG = {
|
|
|
29
29
|
* Convert compressed provider to EmailProvider format
|
|
30
30
|
*/
|
|
31
31
|
function convertProviderToEmailProvider(compressedProvider) {
|
|
32
|
+
if (!compressedProvider.type) {
|
|
33
|
+
console.warn(`Missing type for provider ${compressedProvider.id}`);
|
|
34
|
+
}
|
|
32
35
|
const provider = {
|
|
33
36
|
companyProvider: compressedProvider.companyProvider,
|
|
34
37
|
loginUrl: compressedProvider.loginUrl || null,
|
|
35
|
-
domains: compressedProvider.domains || []
|
|
38
|
+
domains: compressedProvider.domains || [],
|
|
39
|
+
type: compressedProvider.type,
|
|
40
|
+
alias: compressedProvider.alias
|
|
36
41
|
};
|
|
37
|
-
//
|
|
38
|
-
|
|
42
|
+
// Include DNS detection patterns for business email services and proxy services
|
|
43
|
+
const needsCustomDomainDetection = compressedProvider.type === 'custom_provider' ||
|
|
44
|
+
compressedProvider.type === 'proxy_service';
|
|
45
|
+
if (needsCustomDomainDetection && (compressedProvider.mx?.length || compressedProvider.txt?.length)) {
|
|
39
46
|
provider.customDomainDetection = {};
|
|
40
47
|
if (compressedProvider.mx?.length) {
|
|
41
48
|
provider.customDomainDetection.mxPatterns = compressedProvider.mx;
|
package/dist/schema.d.ts
CHANGED
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
* Provider interface
|
|
9
9
|
* Uses compact field names for smaller JSON size
|
|
10
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* Provider types:
|
|
13
|
+
* - public_provider: Regular email providers (Gmail, Yahoo, etc.)
|
|
14
|
+
* - custom_provider: Business email services (Google Workspace, Microsoft 365)
|
|
15
|
+
* - proxy_service: Email proxy services (Cloudflare, etc.)
|
|
16
|
+
*/
|
|
17
|
+
export type ProviderType = 'public_provider' | 'custom_provider' | 'proxy_service';
|
|
11
18
|
export interface Provider {
|
|
12
19
|
/** Provider ID (short identifier) */
|
|
13
20
|
id: string;
|
|
@@ -20,10 +27,22 @@ export interface Provider {
|
|
|
20
27
|
/** DNS detection patterns (flattened) */
|
|
21
28
|
mx?: string[];
|
|
22
29
|
txt?: string[];
|
|
23
|
-
/**
|
|
30
|
+
/** Provider type */
|
|
31
|
+
type: ProviderType;
|
|
32
|
+
/** Alias rules for username part */
|
|
24
33
|
alias?: {
|
|
25
|
-
dots?:
|
|
26
|
-
|
|
34
|
+
dots?: {
|
|
35
|
+
ignore: boolean;
|
|
36
|
+
strip: boolean;
|
|
37
|
+
};
|
|
38
|
+
plus?: {
|
|
39
|
+
ignore: boolean;
|
|
40
|
+
strip: boolean;
|
|
41
|
+
};
|
|
42
|
+
case?: {
|
|
43
|
+
ignore: boolean;
|
|
44
|
+
strip: boolean;
|
|
45
|
+
};
|
|
27
46
|
};
|
|
28
47
|
}
|
|
29
48
|
/**
|
package/dist/url-validator.js
CHANGED
|
@@ -9,106 +9,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
exports.validateEmailProviderUrl = validateEmailProviderUrl;
|
|
10
10
|
exports.validateAllProviderUrls = validateAllProviderUrls;
|
|
11
11
|
exports.auditProviderSecurity = auditProviderSecurity;
|
|
12
|
+
const loader_1 = require("./loader");
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
+
* Get allowlisted domains from provider data
|
|
14
15
|
* Only URLs from these domains will be considered safe.
|
|
15
|
-
*
|
|
16
|
-
* NOTE: This list should be maintained carefully and updated only
|
|
17
|
-
* through security review processes.
|
|
18
16
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
'yahoo.fr',
|
|
37
|
-
'yahoo.de',
|
|
38
|
-
'login.yahoo.com',
|
|
39
|
-
// Privacy-focused providers
|
|
40
|
-
'proton.me',
|
|
41
|
-
'protonmail.com',
|
|
42
|
-
'protonmail.ch',
|
|
43
|
-
'tutanota.com',
|
|
44
|
-
'tutanota.de',
|
|
45
|
-
'posteo.de',
|
|
46
|
-
'runbox.com',
|
|
47
|
-
'countermail.com',
|
|
48
|
-
'hushmail.com',
|
|
49
|
-
// Business providers
|
|
50
|
-
'zoho.com',
|
|
51
|
-
'fastmail.com',
|
|
52
|
-
'rackspace.com',
|
|
53
|
-
'apps.rackspace.com',
|
|
54
|
-
// Other legitimate providers
|
|
55
|
-
'aol.com',
|
|
56
|
-
'mail.aol.com',
|
|
57
|
-
'gmx.com',
|
|
58
|
-
'gmx.net',
|
|
59
|
-
'mail.com',
|
|
60
|
-
'yandex.com',
|
|
61
|
-
'yandex.ru',
|
|
62
|
-
'web.de',
|
|
63
|
-
'mail.ru',
|
|
64
|
-
'libero.it',
|
|
65
|
-
'orange.fr',
|
|
66
|
-
'free.fr',
|
|
67
|
-
't-online.de',
|
|
68
|
-
'comcast.net',
|
|
69
|
-
'att.net',
|
|
70
|
-
'verizon.net',
|
|
71
|
-
'bluehost.com',
|
|
72
|
-
'godaddy.com',
|
|
73
|
-
'secureserver.net',
|
|
74
|
-
// Additional providers from security audit
|
|
75
|
-
'kolabnow.com',
|
|
76
|
-
'connect.xfinity.com',
|
|
77
|
-
'login.verizon.com',
|
|
78
|
-
'www.simply.com',
|
|
79
|
-
'www.one.com',
|
|
80
|
-
'mailfence.com',
|
|
81
|
-
'neo.space',
|
|
82
|
-
'mail.126.com',
|
|
83
|
-
'mail.qq.com',
|
|
84
|
-
'mail.sina.com.cn',
|
|
85
|
-
'www.xtra.co.nz',
|
|
86
|
-
'mail.rediff.com',
|
|
87
|
-
'mail.rakuten.co.jp',
|
|
88
|
-
'mail.nifty.com',
|
|
89
|
-
'mail.iij.ad.jp',
|
|
90
|
-
'email.uol.com.br',
|
|
91
|
-
'email.bol.com.br',
|
|
92
|
-
'email.globo.com',
|
|
93
|
-
'webmail.terra.com.br',
|
|
94
|
-
'webmail.movistar.es',
|
|
95
|
-
'webmail.ono.com',
|
|
96
|
-
'webmail.telkom.co.za',
|
|
97
|
-
'webmail.vodacom.co.za',
|
|
98
|
-
'webmail.mtnonline.com',
|
|
99
|
-
'bdmail.net',
|
|
100
|
-
'mail.aamra.com.bd',
|
|
101
|
-
'mail.link3.net',
|
|
102
|
-
'mail.ionos.com',
|
|
103
|
-
'www.icloud.com',
|
|
104
|
-
'icloud.com',
|
|
105
|
-
'mail.hostinger.com',
|
|
106
|
-
'ngx257.inmotionhosting.com',
|
|
107
|
-
'privateemail.com',
|
|
108
|
-
'app.titan.email',
|
|
109
|
-
'tools.siteground.com',
|
|
110
|
-
'portal.hostgator.com'
|
|
111
|
-
];
|
|
17
|
+
function getAllowedDomains() {
|
|
18
|
+
const { providers } = (0, loader_1.loadProviders)();
|
|
19
|
+
const allowedDomains = new Set();
|
|
20
|
+
for (const provider of providers) {
|
|
21
|
+
if (provider.loginUrl) {
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(provider.loginUrl);
|
|
24
|
+
allowedDomains.add(url.hostname);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Skip invalid URLs
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return allowedDomains;
|
|
33
|
+
}
|
|
112
34
|
/**
|
|
113
35
|
* Suspicious URL patterns that should always be rejected
|
|
114
36
|
*/
|
|
@@ -208,12 +130,9 @@ function validateEmailProviderUrl(url) {
|
|
|
208
130
|
domain
|
|
209
131
|
};
|
|
210
132
|
}
|
|
211
|
-
// Check
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
return domain === allowedDomain || domain.endsWith(`.${allowedDomain}`);
|
|
215
|
-
});
|
|
216
|
-
if (!isAllowed) {
|
|
133
|
+
// Check if the domain is allowed
|
|
134
|
+
const allowedDomains = getAllowedDomains();
|
|
135
|
+
if (!allowedDomains.has(domain)) {
|
|
217
136
|
return {
|
|
218
137
|
isValid: false,
|
|
219
138
|
reason: `Domain '${domain}' is not in the allowlist`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikkelscheike/email-provider-links",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.1",
|
|
4
4
|
"description": "TypeScript library for email provider detection with 93 providers (207 domains), concurrent DNS resolution, optimized performance, 91.75% test coverage, and enterprise security for login and password reset flows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
"!dist/**/*.map"
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
|
+
"clean": "rm -rf dist",
|
|
14
15
|
"verify-hashes": "tsx scripts/verify-hashes.ts",
|
|
15
|
-
"build": "tsx scripts/verify-hashes.ts && tsc",
|
|
16
|
-
"test": "jest
|
|
16
|
+
"build": "npm run clean && tsx scripts/verify-hashes.ts && tsc",
|
|
17
|
+
"test": "jest",
|
|
17
18
|
"test:watch": "jest --watch",
|
|
18
19
|
"test:coverage": "jest --coverage",
|
|
19
20
|
"prepublishOnly": "npm run verify-hashes && npm run build",
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "2.0",
|
|
3
3
|
"providers": [
|
|
4
|
+
{
|
|
5
|
+
"id": "cloudflare",
|
|
6
|
+
"mx": [
|
|
7
|
+
"mx.cloudflare.com",
|
|
8
|
+
"cloudflare.com",
|
|
9
|
+
"mxrecord.io",
|
|
10
|
+
"mxrecord.mx"
|
|
11
|
+
],
|
|
12
|
+
"txt": [
|
|
13
|
+
"spf:_spf.cloudflare.com",
|
|
14
|
+
"cloudflare-verify="
|
|
15
|
+
],
|
|
16
|
+
"type": "proxy_service",
|
|
17
|
+
"companyProvider": "Cloudflare Email Routing",
|
|
18
|
+
"loginUrl": null
|
|
19
|
+
},
|
|
4
20
|
{
|
|
5
21
|
"id": "aamra",
|
|
6
22
|
"domains": [
|
|
@@ -25,7 +41,7 @@
|
|
|
25
41
|
"spf:amazonses.com",
|
|
26
42
|
"spf:workmail.us-east-1.amazonaws.com"
|
|
27
43
|
],
|
|
28
|
-
"type": "
|
|
44
|
+
"type": "custom_provider",
|
|
29
45
|
"companyProvider": "Amazon WorkMail",
|
|
30
46
|
"loginUrl": "https://workmail.aws.amazon.com/"
|
|
31
47
|
},
|
|
@@ -50,7 +66,21 @@
|
|
|
50
66
|
],
|
|
51
67
|
"type": "public_provider",
|
|
52
68
|
"companyProvider": "AOL Mail",
|
|
53
|
-
"loginUrl": "https://mail.aol.com"
|
|
69
|
+
"loginUrl": "https://mail.aol.com",
|
|
70
|
+
"alias": {
|
|
71
|
+
"dots": {
|
|
72
|
+
"ignore": false,
|
|
73
|
+
"strip": false
|
|
74
|
+
},
|
|
75
|
+
"plus": {
|
|
76
|
+
"ignore": false,
|
|
77
|
+
"strip": false
|
|
78
|
+
},
|
|
79
|
+
"case": {
|
|
80
|
+
"ignore": true,
|
|
81
|
+
"strip": true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
54
84
|
},
|
|
55
85
|
{
|
|
56
86
|
"id": "att",
|
|
@@ -95,7 +125,7 @@
|
|
|
95
125
|
"txt": [
|
|
96
126
|
"spf:spf.bluehost.com"
|
|
97
127
|
],
|
|
98
|
-
"type": "
|
|
128
|
+
"type": "custom_provider",
|
|
99
129
|
"companyProvider": "Bluehost Email",
|
|
100
130
|
"loginUrl": "https://webmail.bluehost.com"
|
|
101
131
|
},
|
|
@@ -157,7 +187,21 @@
|
|
|
157
187
|
],
|
|
158
188
|
"type": "public_provider",
|
|
159
189
|
"companyProvider": "FastMail",
|
|
160
|
-
"loginUrl": "https://www.fastmail.com"
|
|
190
|
+
"loginUrl": "https://www.fastmail.com",
|
|
191
|
+
"alias": {
|
|
192
|
+
"dots": {
|
|
193
|
+
"ignore": false,
|
|
194
|
+
"strip": false
|
|
195
|
+
},
|
|
196
|
+
"plus": {
|
|
197
|
+
"ignore": true,
|
|
198
|
+
"strip": true
|
|
199
|
+
},
|
|
200
|
+
"case": {
|
|
201
|
+
"ignore": true,
|
|
202
|
+
"strip": true
|
|
203
|
+
}
|
|
204
|
+
}
|
|
161
205
|
},
|
|
162
206
|
{
|
|
163
207
|
"id": "freefr",
|
|
@@ -183,13 +227,23 @@
|
|
|
183
227
|
"gmail.com",
|
|
184
228
|
"googlemail.com"
|
|
185
229
|
],
|
|
186
|
-
"alias": {
|
|
187
|
-
"dots": true,
|
|
188
|
-
"plus": true
|
|
189
|
-
},
|
|
190
230
|
"type": "public_provider",
|
|
191
231
|
"companyProvider": "Gmail",
|
|
192
|
-
"loginUrl": "https://mail.google.com/mail/"
|
|
232
|
+
"loginUrl": "https://mail.google.com/mail/",
|
|
233
|
+
"alias": {
|
|
234
|
+
"dots": {
|
|
235
|
+
"ignore": true,
|
|
236
|
+
"strip": true
|
|
237
|
+
},
|
|
238
|
+
"plus": {
|
|
239
|
+
"ignore": true,
|
|
240
|
+
"strip": true
|
|
241
|
+
},
|
|
242
|
+
"case": {
|
|
243
|
+
"ignore": true,
|
|
244
|
+
"strip": true
|
|
245
|
+
}
|
|
246
|
+
}
|
|
193
247
|
},
|
|
194
248
|
{
|
|
195
249
|
"id": "gmx",
|
|
@@ -241,7 +295,7 @@
|
|
|
241
295
|
"spf:_spf.google.com",
|
|
242
296
|
"gsv:"
|
|
243
297
|
],
|
|
244
|
-
"type": "
|
|
298
|
+
"type": "custom_provider",
|
|
245
299
|
"companyProvider": "Google Workspace",
|
|
246
300
|
"loginUrl": "https://mail.google.com"
|
|
247
301
|
},
|
|
@@ -295,7 +349,7 @@
|
|
|
295
349
|
"loginUrl": "https://www.hushmail.com/signin/"
|
|
296
350
|
},
|
|
297
351
|
{
|
|
298
|
-
|
|
352
|
+
"id": "icloud",
|
|
299
353
|
"domains": [
|
|
300
354
|
"icloud.com",
|
|
301
355
|
"me.com",
|
|
@@ -311,7 +365,21 @@
|
|
|
311
365
|
],
|
|
312
366
|
"type": "public_provider",
|
|
313
367
|
"companyProvider": "iCloud Mail",
|
|
314
|
-
"loginUrl": "https://www.icloud.com/mail"
|
|
368
|
+
"loginUrl": "https://www.icloud.com/mail",
|
|
369
|
+
"alias": {
|
|
370
|
+
"dots": {
|
|
371
|
+
"ignore": false,
|
|
372
|
+
"strip": false
|
|
373
|
+
},
|
|
374
|
+
"plus": {
|
|
375
|
+
"ignore": true,
|
|
376
|
+
"strip": true
|
|
377
|
+
},
|
|
378
|
+
"case": {
|
|
379
|
+
"ignore": true,
|
|
380
|
+
"strip": true
|
|
381
|
+
}
|
|
382
|
+
}
|
|
315
383
|
},
|
|
316
384
|
{
|
|
317
385
|
"id": "iij",
|
|
@@ -416,7 +484,7 @@
|
|
|
416
484
|
"loginUrl": "https://www.mail.bg"
|
|
417
485
|
},
|
|
418
486
|
{
|
|
419
|
-
|
|
487
|
+
"id": "com",
|
|
420
488
|
"domains": [
|
|
421
489
|
"mail.com"
|
|
422
490
|
],
|
|
@@ -431,7 +499,21 @@
|
|
|
431
499
|
],
|
|
432
500
|
"type": "public_provider",
|
|
433
501
|
"companyProvider": "Mail.ru",
|
|
434
|
-
"loginUrl": "https://mail.ru/"
|
|
502
|
+
"loginUrl": "https://mail.ru/",
|
|
503
|
+
"alias": {
|
|
504
|
+
"dots": {
|
|
505
|
+
"ignore": false,
|
|
506
|
+
"strip": false
|
|
507
|
+
},
|
|
508
|
+
"plus": {
|
|
509
|
+
"ignore": true,
|
|
510
|
+
"strip": true
|
|
511
|
+
},
|
|
512
|
+
"case": {
|
|
513
|
+
"ignore": true,
|
|
514
|
+
"strip": true
|
|
515
|
+
}
|
|
516
|
+
}
|
|
435
517
|
},
|
|
436
518
|
{
|
|
437
519
|
"id": "fence",
|
|
@@ -464,7 +546,7 @@
|
|
|
464
546
|
"ms",
|
|
465
547
|
"microsoft-domain-verification"
|
|
466
548
|
],
|
|
467
|
-
"type": "
|
|
549
|
+
"type": "custom_provider",
|
|
468
550
|
"companyProvider": "Microsoft 365 (Business)",
|
|
469
551
|
"loginUrl": "https://outlook.office365.com"
|
|
470
552
|
},
|
|
@@ -487,12 +569,23 @@
|
|
|
487
569
|
"live.com.au",
|
|
488
570
|
"live.ca"
|
|
489
571
|
],
|
|
490
|
-
"alias": {
|
|
491
|
-
"plus": true
|
|
492
|
-
},
|
|
493
572
|
"type": "public_provider",
|
|
494
573
|
"companyProvider": "Microsoft Outlook",
|
|
495
|
-
"loginUrl": "https://outlook.office365.com"
|
|
574
|
+
"loginUrl": "https://outlook.office365.com",
|
|
575
|
+
"alias": {
|
|
576
|
+
"dots": {
|
|
577
|
+
"ignore": false,
|
|
578
|
+
"strip": false
|
|
579
|
+
},
|
|
580
|
+
"plus": {
|
|
581
|
+
"ignore": true,
|
|
582
|
+
"strip": true
|
|
583
|
+
},
|
|
584
|
+
"case": {
|
|
585
|
+
"ignore": true,
|
|
586
|
+
"strip": true
|
|
587
|
+
}
|
|
588
|
+
}
|
|
496
589
|
},
|
|
497
590
|
{
|
|
498
591
|
"id": "migadu",
|
|
@@ -604,7 +697,7 @@
|
|
|
604
697
|
"txt": [
|
|
605
698
|
"include:_spf.one.com"
|
|
606
699
|
],
|
|
607
|
-
"type": "
|
|
700
|
+
"type": "custom_provider",
|
|
608
701
|
"companyProvider": "One.com Email",
|
|
609
702
|
"loginUrl": "https://www.one.com/mail/"
|
|
610
703
|
},
|
|
@@ -659,12 +752,23 @@
|
|
|
659
752
|
"spf:_spf.protonmail.ch",
|
|
660
753
|
"protonmail-verification="
|
|
661
754
|
],
|
|
662
|
-
"alias": {
|
|
663
|
-
"plus": true
|
|
664
|
-
},
|
|
665
755
|
"type": "public_provider",
|
|
666
756
|
"companyProvider": "ProtonMail",
|
|
667
|
-
"loginUrl": "https://mail.proton.me"
|
|
757
|
+
"loginUrl": "https://mail.proton.me",
|
|
758
|
+
"alias": {
|
|
759
|
+
"dots": {
|
|
760
|
+
"ignore": false,
|
|
761
|
+
"strip": false
|
|
762
|
+
},
|
|
763
|
+
"plus": {
|
|
764
|
+
"ignore": true,
|
|
765
|
+
"strip": true
|
|
766
|
+
},
|
|
767
|
+
"case": {
|
|
768
|
+
"ignore": true,
|
|
769
|
+
"strip": true
|
|
770
|
+
}
|
|
771
|
+
}
|
|
668
772
|
},
|
|
669
773
|
{
|
|
670
774
|
"id": "qq",
|
|
@@ -686,7 +790,7 @@
|
|
|
686
790
|
"txt": [
|
|
687
791
|
"spf:emailsrvr.com"
|
|
688
792
|
],
|
|
689
|
-
"type": "
|
|
793
|
+
"type": "custom_provider",
|
|
690
794
|
"companyProvider": "Rackspace Email",
|
|
691
795
|
"loginUrl": "https://apps.rackspace.com/index.php"
|
|
692
796
|
},
|
|
@@ -756,7 +860,7 @@
|
|
|
756
860
|
"txt": [
|
|
757
861
|
"include:spf.key-systems.net"
|
|
758
862
|
],
|
|
759
|
-
"type": "
|
|
863
|
+
"type": "custom_provider",
|
|
760
864
|
"companyProvider": "Simply.com Email",
|
|
761
865
|
"loginUrl": "https://www.simply.com/en/email"
|
|
762
866
|
},
|
|
@@ -833,7 +937,7 @@
|
|
|
833
937
|
"loginUrl": "https://app.titan.email"
|
|
834
938
|
},
|
|
835
939
|
{
|
|
836
|
-
|
|
940
|
+
"id": "tutano",
|
|
837
941
|
"domains": [
|
|
838
942
|
"tutanota.com",
|
|
839
943
|
"tutanota.de",
|
|
@@ -851,7 +955,21 @@
|
|
|
851
955
|
],
|
|
852
956
|
"type": "public_provider",
|
|
853
957
|
"companyProvider": "Tutanota",
|
|
854
|
-
"loginUrl": "https://mail.tutanota.com"
|
|
958
|
+
"loginUrl": "https://mail.tutanota.com",
|
|
959
|
+
"alias": {
|
|
960
|
+
"dots": {
|
|
961
|
+
"ignore": false,
|
|
962
|
+
"strip": false
|
|
963
|
+
},
|
|
964
|
+
"plus": {
|
|
965
|
+
"ignore": false,
|
|
966
|
+
"strip": false
|
|
967
|
+
},
|
|
968
|
+
"case": {
|
|
969
|
+
"ignore": true,
|
|
970
|
+
"strip": true
|
|
971
|
+
}
|
|
972
|
+
}
|
|
855
973
|
},
|
|
856
974
|
{
|
|
857
975
|
"id": "uol",
|
|
@@ -921,19 +1039,44 @@
|
|
|
921
1039
|
"rocketmail.com",
|
|
922
1040
|
"myyahoo.com"
|
|
923
1041
|
],
|
|
924
|
-
"alias": {
|
|
925
|
-
"plus": true
|
|
926
|
-
},
|
|
927
1042
|
"type": "public_provider",
|
|
928
1043
|
"companyProvider": "Yahoo Mail",
|
|
929
|
-
"loginUrl": "https://login.yahoo.com"
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1044
|
+
"loginUrl": "https://login.yahoo.com",
|
|
1045
|
+
"alias": {
|
|
1046
|
+
"dots": {
|
|
1047
|
+
"ignore": false,
|
|
1048
|
+
"strip": false
|
|
1049
|
+
},
|
|
1050
|
+
"plus": {
|
|
1051
|
+
"ignore": true,
|
|
1052
|
+
"strip": true
|
|
1053
|
+
},
|
|
1054
|
+
"case": {
|
|
1055
|
+
"ignore": true,
|
|
1056
|
+
"strip": true
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
{
|
|
1061
|
+
"id": "yandex",
|
|
933
1062
|
"domains": [
|
|
934
1063
|
"yandex.ru",
|
|
935
1064
|
"yandex.com"
|
|
936
1065
|
],
|
|
1066
|
+
"alias": {
|
|
1067
|
+
"dots": {
|
|
1068
|
+
"ignore": false,
|
|
1069
|
+
"strip": false
|
|
1070
|
+
},
|
|
1071
|
+
"plus": {
|
|
1072
|
+
"ignore": true,
|
|
1073
|
+
"strip": true
|
|
1074
|
+
},
|
|
1075
|
+
"case": {
|
|
1076
|
+
"ignore": true,
|
|
1077
|
+
"strip": true
|
|
1078
|
+
}
|
|
1079
|
+
},
|
|
937
1080
|
"type": "public_provider",
|
|
938
1081
|
"companyProvider": "Yandex Mail",
|
|
939
1082
|
"loginUrl": "https://mail.yandex.com"
|
|
@@ -946,10 +1089,24 @@
|
|
|
946
1089
|
],
|
|
947
1090
|
"type": "public_provider",
|
|
948
1091
|
"companyProvider": "Zoho Mail",
|
|
949
|
-
"loginUrl": "https://mail.zoho.com"
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1092
|
+
"loginUrl": "https://mail.zoho.com",
|
|
1093
|
+
"alias": {
|
|
1094
|
+
"dots": {
|
|
1095
|
+
"ignore": false,
|
|
1096
|
+
"strip": false
|
|
1097
|
+
},
|
|
1098
|
+
"plus": {
|
|
1099
|
+
"ignore": true,
|
|
1100
|
+
"strip": true
|
|
1101
|
+
},
|
|
1102
|
+
"case": {
|
|
1103
|
+
"ignore": true,
|
|
1104
|
+
"strip": true
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
1108
|
+
{
|
|
1109
|
+
"id": "zohoeu",
|
|
953
1110
|
"domains": [
|
|
954
1111
|
"zoho.eu"
|
|
955
1112
|
],
|