valid_email_checker 0.1.0

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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +24 -0
  3. data/Cargo.lock +2940 -0
  4. data/Cargo.toml +22 -0
  5. data/LICENSE.txt +661 -0
  6. data/NOTICE.md +56 -0
  7. data/README.md +316 -0
  8. data/exe/valid_email_checker +148 -0
  9. data/ext/valid_email_checker/Cargo.toml +27 -0
  10. data/ext/valid_email_checker/extconf.rb +26 -0
  11. data/ext/valid_email_checker/src/lib.rs +371 -0
  12. data/ext/valid_email_checker/src/nogvl.rs +64 -0
  13. data/ext/valid_email_checker/vendor/UPSTREAM.json +9 -0
  14. data/ext/valid_email_checker/vendor/check-if-email-exists/Cargo.toml +52 -0
  15. data/ext/valid_email_checker/vendor/check-if-email-exists/LICENSE.AGPL +661 -0
  16. data/ext/valid_email_checker/vendor/check-if-email-exists/LICENSE.md +11 -0
  17. data/ext/valid_email_checker/vendor/check-if-email-exists/README.md +175 -0
  18. data/ext/valid_email_checker/vendor/check-if-email-exists/src/haveibeenpwned.rs +70 -0
  19. data/ext/valid_email_checker/vendor/check-if-email-exists/src/lib.rs +281 -0
  20. data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/b2c.txt +96640 -0
  21. data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/gravatar.rs +60 -0
  22. data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/mod.rs +124 -0
  23. data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/roles.txt +944 -0
  24. data/ext/valid_email_checker/vendor/check-if-email-exists/src/mx/mod.rs +165 -0
  25. data/ext/valid_email_checker/vendor/check-if-email-exists/src/rules.json +28 -0
  26. data/ext/valid_email_checker/vendor/check-if-email-exists/src/rules.rs +105 -0
  27. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/connect.rs +396 -0
  28. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/error.rs +144 -0
  29. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/gmail.rs +99 -0
  30. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/headless.rs +82 -0
  31. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/http_api.rs +27 -0
  32. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/mod.rs +234 -0
  33. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/outlook/headless.rs +181 -0
  34. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/outlook/microsoft365.rs +109 -0
  35. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/outlook/mod.rs +2 -0
  36. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/parser.rs +291 -0
  37. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/verif_method.rs +531 -0
  38. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/yahoo/api.rs +174 -0
  39. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/yahoo/headless.rs +188 -0
  40. data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/yahoo/mod.rs +62 -0
  41. data/ext/valid_email_checker/vendor/check-if-email-exists/src/syntax/mod.rs +199 -0
  42. data/ext/valid_email_checker/vendor/check-if-email-exists/src/syntax/normalize.rs +70 -0
  43. data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/input_output.rs +353 -0
  44. data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/mod.rs +20 -0
  45. data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/sentry.rs +173 -0
  46. data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/ser_with_display.rs +28 -0
  47. data/lib/valid_email_checker/configuration.rb +216 -0
  48. data/lib/valid_email_checker/errors.rb +32 -0
  49. data/lib/valid_email_checker/result.rb +241 -0
  50. data/lib/valid_email_checker/syntax.rb +72 -0
  51. data/lib/valid_email_checker/version.rb +5 -0
  52. data/lib/valid_email_checker.rb +217 -0
  53. metadata +118 -0
@@ -0,0 +1,175 @@
1
+ [![Crate](https://img.shields.io/crates/v/check-if-email-exists.svg)](https://crates.io/crates/check-if-email-exists)
2
+ [![Docs](https://docs.rs/check-if-email-exists/badge.svg)](https://docs.rs/check-if-email-exists)
3
+ [![Docker](https://img.shields.io/docker/v/reacherhq/backend?color=0db7ed&label=docker&sort=date)](https://hub.docker.com/r/reacherhq/backend)
4
+ [![Actions Status](https://github.com/reacherhq/check-if-email-exists/workflows/pr/badge.svg)](https://github.com/reacherhq/check-if-email-exists/actions)
5
+
6
+ <br /><br />
7
+
8
+ <p align="center"><img align="center" src="https://storage.googleapis.com/saasify-uploads-prod/696e287ad79f0e0352bc201b36d701849f7d55e7.svg" height="96" alt="reacher" /></p>
9
+ <h1 align="center">check-if-email-exists</h1>
10
+ <h4 align="center">Check if an email address exists without sending any email.<br/>Comes with a <a href="./backend">⚙️ HTTP backend</a>.</h4>
11
+
12
+ <br /><br /><br />
13
+
14
+ ## 👉 Live Demo: https://reacher.email
15
+
16
+ <img src="https://storage.googleapis.com/saasify-uploads-prod/696e287ad79f0e0352bc201b36d701849f7d55e7.svg" height="68" align="left" />
17
+
18
+ This is open-source, but I also offer a **SaaS** solution that has `check-if-email-exists` packaged in a nice friendly web interface. If you are interested, find out more at [No2Bounce.com](https://no2bounce.com/?ref=github). If you have any questions, you can contact me at amaury@reacher.email.
19
+
20
+ <br />
21
+
22
+ ## Get Started
23
+
24
+ 3 non-SaaS ways to get started with `check-if-email-exists`.
25
+
26
+ ### 1. ⚙️ HTTP backend using Docker (popular method 🥇) [[Full docs](./backend/README.md)]
27
+
28
+ This option allows you to run a HTTP backend using Docker 🐳, on a cloud instance or your own server. Please note that outbound port 25 must be open.
29
+
30
+ ```bash
31
+ docker run -p 8080:8080 reacherhq/backend:latest
32
+ ```
33
+
34
+ Then send a `POST http://localhost:8080/v0/check_email` request with the following body:
35
+
36
+ ```js
37
+ {
38
+ "to_email": "someone@gmail.com",
39
+ "proxy": { // (optional) SOCK5 proxy to run the verification through, default is empty
40
+ "host": "my-proxy.io",
41
+ "port": 1080,
42
+ "username": "me", // (optional) Proxy username
43
+ "password": "pass" // (optional) Proxy password
44
+ }
45
+ }
46
+ ```
47
+ **Note regarding proxy servers**
48
+ It is possible to operate Reacher with your own IP addresses. But if you wish to process more than very small volumes you will need SMTP proxy servers. For SMTP proxy servers please use [proxy25.com](https://proxy25.com/?ref=github)
49
+
50
+ ### 2. Download the CLI [[Full docs](./cli/README.md)]
51
+
52
+ > Note: The CLI binary doesn't connect to any backend, it checks the email directly from your computer.
53
+
54
+ Head to the [releases page](https://github.com/reacherhq/check-if-email-exists/releases) and download the binary for your platform.
55
+
56
+ ```bash
57
+ > $ check_if_email_exists --help
58
+ check_if_email_exists 0.9.1
59
+ Check if an email address exists without sending an email.
60
+
61
+ USAGE:
62
+ check_if_email_exists [FLAGS] [OPTIONS] [TO_EMAIL]
63
+ ```
64
+
65
+ Check out the [dedicated README.md](./cli/README.md) for all options and flags.
66
+
67
+ ### 3. Programmatic Usage [[Full docs](https://docs.rs/check-if-email-exists)]
68
+
69
+ In your own Rust project, you can add `check-if-email-exists` in your `Cargo.toml`:
70
+
71
+ ```toml
72
+ [dependencies]
73
+ check-if-email-exists = "0.9"
74
+ ```
75
+
76
+ And use it in your code as follows:
77
+
78
+ ```rust
79
+ use check_if_email_exists::{check_email, CheckEmailInput, CheckEmailInputProxy};
80
+
81
+ async fn check() {
82
+ // Let's say we want to test the deliverability of someone@gmail.com.
83
+ let mut input = CheckEmailInput::new(vec!["someone@gmail.com".into()]);
84
+
85
+ // Verify this email, using async/await syntax.
86
+ let result = check_email(&input).await;
87
+
88
+ // `result` is a `Vec<CheckEmailOutput>`, where the CheckEmailOutput
89
+ // struct contains all information about our email.
90
+ println!("{:?}", result);
91
+ }
92
+ ```
93
+
94
+ The reference docs are hosted on [docs.rs](https://docs.rs/check-if-email-exists).
95
+
96
+ ## ✈️ JSON Output
97
+
98
+ The output will be a JSON with the below format, the fields should be self-explanatory. For `someone@gmail.com` (note that it is disabled by Gmail), here's the exact output:
99
+
100
+ ```json
101
+ {
102
+ "input": "someone@gmail.com",
103
+ "is_reachable": "invalid",
104
+ "misc": {
105
+ "is_disposable": false,
106
+ "is_role_account": false,
107
+ "is_b2c": true
108
+ },
109
+ "mx": {
110
+ "accepts_mail": true,
111
+ "records": [
112
+ "alt3.gmail-smtp-in.l.google.com.",
113
+ "gmail-smtp-in.l.google.com.",
114
+ "alt1.gmail-smtp-in.l.google.com.",
115
+ "alt4.gmail-smtp-in.l.google.com.",
116
+ "alt2.gmail-smtp-in.l.google.com."
117
+ ]
118
+ },
119
+ "smtp": {
120
+ "can_connect_smtp": true,
121
+ "has_full_inbox": false,
122
+ "is_catch_all": false,
123
+ "is_deliverable": false,
124
+ "is_disabled": true
125
+ },
126
+ "syntax": {
127
+ "domain": "gmail.com",
128
+ "is_valid_syntax": true,
129
+ "username": "someone",
130
+ "suggestion": null
131
+ }
132
+ }
133
+ ```
134
+
135
+ ## What Does This Tool Check?
136
+
137
+ | Included? | Feature | Description | JSON field |
138
+ | --------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
139
+ | ✅ | **Email reachability** | How confident are we in sending an email to this address? Can be one of `safe`, `risky`, `invalid` or `unknown`. | `is_reachable` |
140
+ | ✅ | **Syntax validation** | Is the address syntactically valid? | `syntax.is_valid_syntax` |
141
+ | ✅ | **DNS records validation** | Does the domain of the email address have valid MX DNS records? | `mx.accepts_mail` |
142
+ | ✅ | **Disposable email address (DEA) validation** | Is the address provided by a known [disposable email address](https://en.wikipedia.org/wiki/Disposable_email_address) provider? | `misc.is_disposable` |
143
+ | ✅ | **SMTP server validation** | Can the mail exchanger of the email address domain be contacted successfully? | `smtp.can_connect_smtp` |
144
+ | ✅ | **Email deliverability** | Is an email sent to this address deliverable? | `smtp.is_deliverable` |
145
+ | ✅ | **Mailbox disabled** | Has this email address been disabled by the email provider? | `smtp.is_disabled` |
146
+ | ✅ | **Full inbox** | Is the inbox of this mailbox full? | `smtp.has_full_inbox` |
147
+ | ✅ | **Catch-all address** | Is this email address a [catch-all](https://debounce.io/blog/help/what-is-a-catch-all-or-accept-all/) address? | `smtp.is_catch_all` |
148
+ | ✅ | **Role account validation** | Is the email address a well-known role account? | `misc.is_role_account` |
149
+ | ✅ | **Gravatar Url** | The url of the [Gravatar](https://gravatar.com/) email address profile picture | `misc.gravatar_url` |
150
+ | ✅ | **Have I Been Pwned?** | Has this email been compromised in a [data breach](https://haveibeenpwned.com/)? | `misc.haveibeenpwned` |
151
+ | 🔜 | **Free email provider check** | Is the email address bound to a known free email provider? | [Issue #89](https://github.com/reacherhq/check-if-email-exists/issues/89) |
152
+ | 🔜 | **Syntax validation, provider-specific** | According to the syntactic rules of the target mail provider, is the address syntactically valid? | [Issue #90](https://github.com/reacherhq/check-if-email-exists/issues/90) |
153
+ | 🔜 | **Honeypot detection** | Does email address under test hide a [honeypot](https://en.wikipedia.org/wiki/Spamtrap)? | [Issue #91](https://github.com/reacherhq/check-if-email-exists/issues/91) |
154
+
155
+ ## 🤔 Why?
156
+
157
+ Many online services (https://hunter.io, https://verify-email.org, https://email-checker.net) offer this service for a paid fee. Here is an open-source alternative to those tools.
158
+
159
+ ## License
160
+
161
+ `check-if-email-exists`'s source code is provided under a **dual license model**.
162
+
163
+ ### Commercial license
164
+
165
+ If you want to use `check-if-email-exists` to develop commercial sites, tools, and applications, the Commercial License is the appropriate license. With this option, your source code is kept proprietary. Purchase a `check-if-email-exists` Commercial License at https://reacher.email/pricing.
166
+
167
+ ### Open source license
168
+
169
+ If you are creating an open-source application under a license compatible with the GNU Affero GPL License v3, you may use `check-if-email-exists` under the terms of the [AGPL-3.0](./LICENSE.AGPL).
170
+
171
+ [➡️ Read more](https://docs.reacher.email/self-hosting/licensing) about Reacher's license.
172
+
173
+ ## 🔨 Build From Source
174
+
175
+ Build the [CLI from source](./cli/README.md#build-from-source) or the [HTTP backend from source](./backend/README.md#build-from-source).
@@ -0,0 +1,70 @@
1
+ // check-if-email-exists
2
+ // Copyright (C) 2018-2023 Reacher
3
+
4
+ // This program is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU Affero General Public License as published
6
+ // by the Free Software Foundation, either version 3 of the License, or
7
+ // (at your option) any later version.
8
+
9
+ // This program is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU Affero General Public License for more details.
13
+
14
+ // You should have received a copy of the GNU Affero General Public License
15
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ use crate::LOG_TARGET;
18
+ use reqwest::Client;
19
+
20
+ const MAIN_API_URL: &str = "https://haveibeenpwned.com/api/v3/";
21
+
22
+ /// Check if the email has been found in any breach or paste using the
23
+ /// HaveIBeenPwned API.
24
+ /// This function will return the number of times the email has been found in
25
+ /// any breach.
26
+ pub async fn check_haveibeenpwned(to_email: &str, api_key: Option<String>) -> Option<bool> {
27
+ let client = Client::new();
28
+ let url = format!(
29
+ "{}breachedaccount/{}?truncateResponse=false",
30
+ MAIN_API_URL, to_email
31
+ );
32
+
33
+ let request = client
34
+ .get(&url)
35
+ .header("User-Agent", "reacher")
36
+ .header("hibp-api-key", api_key.unwrap_or_default())
37
+ .send()
38
+ .await;
39
+
40
+ match request {
41
+ Ok(response) => {
42
+ if response.status().is_success() {
43
+ let breaches: Vec<serde_json::Value> = response.json().await.unwrap_or_default();
44
+ tracing::debug!(
45
+ target: LOG_TARGET,
46
+ breach_count=breaches.len(),
47
+ "HaveIBeenPwned check completed"
48
+ );
49
+ Some(!breaches.is_empty())
50
+ } else if response.status() == reqwest::StatusCode::NOT_FOUND {
51
+ Some(false)
52
+ } else {
53
+ tracing::error!(
54
+ target: LOG_TARGET,
55
+ status = %response.status(),
56
+ "Error checking HaveIBeenPwned"
57
+ );
58
+ None
59
+ }
60
+ }
61
+ Err(e) => {
62
+ tracing::error!(
63
+ target: LOG_TARGET,
64
+ error=?e,
65
+ "Error checking HaveIBeenPwned"
66
+ );
67
+ None
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,281 @@
1
+ // check-if-email-exists
2
+ // Copyright (C) 2018-2023 Reacher
3
+
4
+ // This program is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU Affero General Public License as published
6
+ // by the Free Software Foundation, either version 3 of the License, or
7
+ // (at your option) any later version.
8
+
9
+ // This program is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU Affero General Public License for more details.
13
+
14
+ // You should have received a copy of the GNU Affero General Public License
15
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ //! `check-if-email-exists` lets you check if an email address exists without
18
+ //! sending any email.
19
+ //!
20
+ //! Under the hood, it connects to the email address's SMTP server, and,
21
+ //! analyzing the server's responses against some SMTP commands, finds out
22
+ //! information about the email address, such as:
23
+ //! - Email deliverability: Is an email sent to this address deliverable?
24
+ //! - Syntax validation. Is the address syntactically valid?
25
+ //! - DNS records validation. Does the domain of the email address have valid
26
+ //! MX DNS records?
27
+ //! - Disposable email address (DEA) validation. Is the address provided by a
28
+ //! known disposable email address provider?
29
+ //! - SMTP server validation. Can the mail exchanger of the email address
30
+ //! domain be contacted successfully?
31
+ //! - Mailbox disabled. Has this email address been disabled by the email
32
+ //! provider?
33
+ //! - Full inbox. Is the inbox of this mailbox full?
34
+ //! - Catch-all address. Is this email address a catch-all address?
35
+ //!
36
+ //! ```rust
37
+ //! use check_if_email_exists::{check_email, CheckEmailInputBuilder, CheckEmailInputProxy};
38
+ //! use check_if_email_exists::smtp::verif_method::{VerifMethod, VerifMethodSmtpConfig, GmailVerifMethod};
39
+ //! use std::collections::HashMap;
40
+ //!
41
+ //! async fn check() {
42
+ //! // Let's say we want to test the deliverability of someone@gmail.com.
43
+ //!
44
+ //! // We can tweak how we want to verify the email address, though using
45
+ //! // the default values is usually enough. However, if you want to use a
46
+ //! // proxy, you can do so like this:
47
+ //! let mut proxies = HashMap::new();
48
+ //! proxies.insert("proxy1".to_string(), CheckEmailInputProxy {
49
+ //! host: "my-proxy.io".to_string(), // Use a SOCKS5 proxy to verify the email
50
+ //! port: 1080,
51
+ //! username: None, // You can also set it non-empty
52
+ //! password: None,
53
+ //! timeout_ms: None,
54
+ //! });
55
+ //! let verif_method = VerifMethod {
56
+ //! proxies,
57
+ //! gmail: GmailVerifMethod::Smtp(VerifMethodSmtpConfig {
58
+ //! from_email: "me@example.org".to_string(), // Used in the `MAIL FROM:` command
59
+ //! hello_name: "example.org".to_string(), // Used in the `EHLO` command
60
+ //! smtp_port: 587, // Use port 587 instead of 25
61
+ //! proxy: Some("proxy1".to_string()), // Use the proxy we defined above
62
+ //! ..Default::default()
63
+ //! }),
64
+ //! ..Default::default()
65
+ //! };
66
+ //!
67
+ //! let input = CheckEmailInputBuilder::default()
68
+ //! .to_email("someone@gmail.com".into())
69
+ //! .verif_method(verif_method)
70
+ //! .build()
71
+ //! .unwrap();
72
+ //!
73
+ //! // Verify this input, using async/await syntax.
74
+ //! let result = check_email(&input).await;
75
+ //!
76
+ //! // `result` is a `Vec<CheckEmailOutput>`, where the CheckEmailOutput
77
+ //! // struct contains all information about one email.
78
+ //! println!("{:?}", result);
79
+ //! }
80
+ //! ```
81
+
82
+ mod haveibeenpwned;
83
+ pub mod misc;
84
+ pub mod mx;
85
+ mod rules;
86
+ pub mod smtp;
87
+ pub mod syntax;
88
+ mod util;
89
+
90
+ use misc::{check_misc, MiscDetails};
91
+ use mx::check_mx;
92
+ use rustls::crypto::ring;
93
+ use smtp::{check_smtp, SmtpDetails, SmtpError};
94
+ pub use smtp::{is_gmail, is_hotmail, is_hotmail_b2b, is_hotmail_b2c, is_yahoo};
95
+ use std::sync::Once;
96
+ use std::time::{Duration, SystemTime};
97
+ use syntax::{check_syntax, get_similar_mail_provider};
98
+ pub use util::input_output::*;
99
+ #[cfg(feature = "sentry")]
100
+ pub use util::sentry::*;
101
+
102
+ /// The target where to log check-if-email-exists logs.
103
+ pub const LOG_TARGET: &str = "reacher";
104
+
105
+ static INIT: Once = Once::new();
106
+
107
+ /// check-if-email-exists uses rustls for its TLS connections. This function
108
+ /// initializes the default crypto provider for rustls.
109
+ pub fn initialize_crypto_provider() {
110
+ INIT.call_once(|| {
111
+ ring::default_provider().install_default().unwrap();
112
+ });
113
+ }
114
+
115
+ /// Given an email's misc and smtp details, calculate an estimate of our
116
+ /// confidence on how reachable the email is.
117
+ ///
118
+ /// Maybe we can switch to a points-based system?
119
+ /// ref: https://github.com/reacherhq/check-if-email-exists/issues/935
120
+ fn calculate_reachable(misc: &MiscDetails, smtp: &Result<SmtpDetails, SmtpError>) -> Reachable {
121
+ if let Ok(smtp) = smtp {
122
+ if misc.is_disposable || misc.is_role_account || smtp.is_catch_all || smtp.has_full_inbox {
123
+ return Reachable::Risky;
124
+ }
125
+
126
+ if !smtp.is_deliverable || !smtp.can_connect_smtp || smtp.is_disabled {
127
+ return Reachable::Invalid;
128
+ }
129
+
130
+ Reachable::Safe
131
+ } else {
132
+ Reachable::Unknown
133
+ }
134
+ }
135
+
136
+ /// The main function of this library: verify a single email. Performs, in the
137
+ /// following order, 4 types of verifications:
138
+ /// - syntax check: verify the email is well-formed,
139
+ /// - MX checks: verify the domain is configured to receive email,
140
+ /// - SMTP checks: connect to the SMTP server and verify the email is
141
+ /// deliverable,
142
+ /// - misc checks: metadata about the email provider.
143
+ ///
144
+ /// Returns a `CheckEmailOutput` output, whose `is_reachable` field is one of
145
+ /// `Safe`, `Invalid`, `Risky` or `Unknown`.
146
+ pub async fn check_email(input: &CheckEmailInput) -> CheckEmailOutput {
147
+ initialize_crypto_provider();
148
+ let start_time = SystemTime::now();
149
+ let to_email = &input.to_email;
150
+
151
+ tracing::debug!(
152
+ target: LOG_TARGET,
153
+ email=%to_email,
154
+ "Checking email"
155
+ );
156
+ let mut my_syntax = check_syntax(to_email.as_ref());
157
+ if !my_syntax.is_valid_syntax {
158
+ return CheckEmailOutput {
159
+ input: to_email.to_string(),
160
+ is_reachable: Reachable::Invalid,
161
+ syntax: my_syntax,
162
+ ..Default::default()
163
+ };
164
+ }
165
+
166
+ tracing::debug!(
167
+ target: LOG_TARGET,
168
+ email=%to_email,
169
+ syntax=?my_syntax,
170
+ "Found syntax validation"
171
+ );
172
+
173
+ let my_mx = match check_mx(&my_syntax).await {
174
+ Ok(m) => m,
175
+ e => {
176
+ get_similar_mail_provider(&mut my_syntax);
177
+
178
+ // This happens when there's an internal error while checking MX
179
+ // records. Should happen fairly rarely.
180
+ return CheckEmailOutput {
181
+ input: to_email.to_string(),
182
+ is_reachable: Reachable::Unknown,
183
+ mx: e,
184
+ syntax: my_syntax,
185
+ ..Default::default()
186
+ };
187
+ }
188
+ };
189
+
190
+ // Return if we didn't find any MX records.
191
+ if my_mx.lookup.is_err() {
192
+ get_similar_mail_provider(&mut my_syntax);
193
+
194
+ return CheckEmailOutput {
195
+ input: to_email.to_string(),
196
+ is_reachable: Reachable::Invalid,
197
+ mx: Ok(my_mx),
198
+ syntax: my_syntax,
199
+ ..Default::default()
200
+ };
201
+ }
202
+
203
+ let mx_hosts: Vec<String> = my_mx
204
+ .lookup
205
+ .as_ref()
206
+ .expect("If lookup is error, we already returned. qed.")
207
+ .iter()
208
+ .map(|host| host.to_string())
209
+ .collect();
210
+
211
+ tracing::debug!(
212
+ target: LOG_TARGET,
213
+ email=%to_email,
214
+ mx_hosts=?mx_hosts,
215
+ "Found MX hosts"
216
+ );
217
+
218
+ let my_misc = check_misc(
219
+ &my_syntax,
220
+ input.check_gravatar,
221
+ input.haveibeenpwned_api_key.clone(),
222
+ )
223
+ .await;
224
+
225
+ tracing::debug!(
226
+ target: LOG_TARGET,
227
+ email=%to_email,
228
+ misc=?my_misc,
229
+ "Found misc details"
230
+ );
231
+
232
+ // From the list of MX records, we choose the one with the lowest priority.
233
+ let mx_records = my_mx
234
+ .lookup
235
+ .as_ref()
236
+ .expect("If lookup is error, we already returned. qed.")
237
+ .iter()
238
+ .min_by_key(|a| a.preference())
239
+ .expect("There should be at least one MX record after filtering.");
240
+ let host = mx_records;
241
+
242
+ let (my_smtp, smtp_debug) = check_smtp(
243
+ my_syntax
244
+ .address
245
+ .as_ref()
246
+ .expect("We already checked that the email has valid format. qed."),
247
+ host.exchange(),
248
+ my_syntax.domain.as_ref(),
249
+ input,
250
+ )
251
+ .await;
252
+
253
+ if my_smtp.is_err() {
254
+ get_similar_mail_provider(&mut my_syntax);
255
+ }
256
+
257
+ let end_time = SystemTime::now();
258
+
259
+ let output = CheckEmailOutput {
260
+ input: to_email.to_string(),
261
+ is_reachable: calculate_reachable(&my_misc, &my_smtp),
262
+ misc: Ok(my_misc),
263
+ mx: Ok(my_mx),
264
+ smtp: my_smtp,
265
+ syntax: my_syntax,
266
+ debug: DebugDetails {
267
+ start_time: start_time.into(),
268
+ end_time: end_time.into(),
269
+ duration: end_time
270
+ .duration_since(start_time)
271
+ .unwrap_or(Duration::from_secs(0)),
272
+ smtp: smtp_debug,
273
+ backend_name: input.backend_name.clone(),
274
+ },
275
+ };
276
+
277
+ #[cfg(feature = "sentry")]
278
+ log_unknown_errors(&output, &input.backend_name);
279
+
280
+ output
281
+ }