deliverable 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/lib/deliverable/result.rb +11 -2
- data/lib/deliverable/verifier.rb +60 -14
- data/lib/deliverable/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e68d3c1f29cb9fac64f7185d0b4026f6d8f9bcf5a6e55d9e7d89b3b3ab126ae4
|
|
4
|
+
data.tar.gz: e6ca8b392fef7e5289586a2c774684e31b4330b23b2c20dea30571ed256331b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 72cc381a74af672304ae18c4d5523dd85ed5174e33638a4958f709da2cbe9f5fd3dc34defa2ce02842a59c11e3c3862e8ddaeb15dba4f587d5bd0911a2337b3c
|
|
7
|
+
data.tar.gz: d710009b9129b3dfc8014bebc374edf586ea3c3ce0aa0ae1390fa14f8fb274022bbeac79f6e14cd4d660dd139649918ee1eab93c7749add82c204361182a1eab
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
- Fix: the catch-all probe was overwriting `smtp_response` and
|
|
6
|
+
`retryable` on the Result. A verification that returned a clean 250
|
|
7
|
+
for the real address could surface a 550 from the fake address used
|
|
8
|
+
to detect accept-all servers, making it look like the verifier had
|
|
9
|
+
ignored a hard bounce. The catch-all probe now runs silently and
|
|
10
|
+
only the real address's SMTP response is reported.
|
|
11
|
+
|
|
12
|
+
## 0.2.0
|
|
13
|
+
|
|
14
|
+
- Add `smtp_response` to the result: the SMTP code and message returned by
|
|
15
|
+
the destination MX (e.g. `{ code: "421", message: "Greylisted, try again" }`).
|
|
16
|
+
`nil` when no SMTP probe was made or no response was received.
|
|
17
|
+
- Add `retryable` boolean to the result. `true` when the verifier hit a
|
|
18
|
+
transient signal (4xx response, server-busy, timeout, connection refused, or
|
|
19
|
+
a transient DNS error) and the result is `risky`. Callers can use this to
|
|
20
|
+
decide whether to enqueue a follow-up verification.
|
|
21
|
+
|
|
3
22
|
## 0.1.0
|
|
4
23
|
|
|
5
24
|
- Initial release.
|
data/lib/deliverable/result.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
module Deliverable
|
|
2
2
|
class Result
|
|
3
|
-
attr_reader :email, :score, :score_details, :errors, :warnings, :checks
|
|
3
|
+
attr_reader :email, :score, :score_details, :errors, :warnings, :checks, :smtp_response
|
|
4
4
|
|
|
5
|
-
def initialize(email:, score:, score_details:, classification:, errors:, warnings:, checks:)
|
|
5
|
+
def initialize(email:, score:, score_details:, classification:, errors:, warnings:, checks:, smtp_response: nil, retryable: false)
|
|
6
6
|
@email = email
|
|
7
7
|
@score = score
|
|
8
8
|
@score_details = score_details
|
|
@@ -10,6 +10,8 @@ module Deliverable
|
|
|
10
10
|
@errors = errors
|
|
11
11
|
@warnings = warnings
|
|
12
12
|
@checks = checks
|
|
13
|
+
@smtp_response = smtp_response
|
|
14
|
+
@retryable = retryable
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
def valid?
|
|
@@ -20,6 +22,11 @@ module Deliverable
|
|
|
20
22
|
@classification
|
|
21
23
|
end
|
|
22
24
|
|
|
25
|
+
def retryable?
|
|
26
|
+
@retryable
|
|
27
|
+
end
|
|
28
|
+
alias_method :retryable, :retryable?
|
|
29
|
+
|
|
23
30
|
def to_h
|
|
24
31
|
{
|
|
25
32
|
email: @email,
|
|
@@ -29,6 +36,8 @@ module Deliverable
|
|
|
29
36
|
score_details: @score_details,
|
|
30
37
|
errors: @errors,
|
|
31
38
|
warnings: @warnings,
|
|
39
|
+
retryable: @retryable,
|
|
40
|
+
smtp_response: @smtp_response,
|
|
32
41
|
checks: @checks
|
|
33
42
|
}
|
|
34
43
|
end
|
data/lib/deliverable/verifier.rb
CHANGED
|
@@ -44,18 +44,23 @@ module Deliverable
|
|
|
44
44
|
accepts_any_email: false
|
|
45
45
|
}
|
|
46
46
|
@accepts_any_email = nil
|
|
47
|
+
@smtp_response = nil
|
|
48
|
+
@smtp_retryable = false
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
def call
|
|
50
52
|
ok = run_checks
|
|
51
53
|
score = calculate_score
|
|
54
|
+
cls = classification(ok)
|
|
52
55
|
Result.new(
|
|
53
56
|
email: @email,
|
|
54
57
|
score: score,
|
|
55
58
|
score_details: @details.dup,
|
|
56
|
-
classification:
|
|
59
|
+
classification: cls,
|
|
57
60
|
errors: @errors,
|
|
58
61
|
warnings: @warnings,
|
|
62
|
+
smtp_response: @smtp_response,
|
|
63
|
+
retryable: retryable?(cls),
|
|
59
64
|
checks: {
|
|
60
65
|
syntax: @details[:syntax_valid],
|
|
61
66
|
mx_record: @details[:mx_verified],
|
|
@@ -217,7 +222,7 @@ module Deliverable
|
|
|
217
222
|
problematic_server?(mail_server) ? :assumed : (last || :assumed)
|
|
218
223
|
end
|
|
219
224
|
|
|
220
|
-
def attempt_rcpt(mail_server, port, recipient)
|
|
225
|
+
def attempt_rcpt(mail_server, port, recipient, record: true)
|
|
221
226
|
Timeout.timeout(@timeout) do
|
|
222
227
|
smtp = Net::SMTP.new(mail_server, port)
|
|
223
228
|
smtp.enable_starttls_auto if port == 587 || port == 465
|
|
@@ -225,44 +230,80 @@ module Deliverable
|
|
|
225
230
|
smtp.start(@sender_domain) do |conn|
|
|
226
231
|
conn.mailfrom(@sender_email)
|
|
227
232
|
begin
|
|
228
|
-
conn.rcptto(recipient)
|
|
233
|
+
response = conn.rcptto(recipient)
|
|
234
|
+
record_response(response.status, response.message) if record && response.respond_to?(:status)
|
|
229
235
|
return :verified
|
|
230
236
|
rescue Net::SMTPFatalError => e
|
|
231
|
-
|
|
232
|
-
|
|
237
|
+
record_error(e) if record
|
|
238
|
+
classify_fatal(e.message, record: record)
|
|
239
|
+
rescue Net::SMTPServerBusy => e
|
|
240
|
+
record_error(e, retryable: true) if record
|
|
241
|
+
return :assumed
|
|
242
|
+
rescue Net::SMTPSyntaxError, Net::SMTPAuthenticationError => e
|
|
243
|
+
record_error(e) if record
|
|
233
244
|
return :assumed
|
|
234
245
|
end
|
|
235
246
|
end
|
|
236
247
|
end
|
|
237
|
-
rescue Timeout::Error
|
|
248
|
+
rescue Timeout::Error => e
|
|
249
|
+
record_transient(e.message) if record
|
|
250
|
+
:connection_failed
|
|
251
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH => e
|
|
252
|
+
record_transient(e.message) if record
|
|
238
253
|
:connection_failed
|
|
239
|
-
rescue Net::SMTPAuthenticationError
|
|
254
|
+
rescue Net::SMTPAuthenticationError => e
|
|
255
|
+
record_error(e) if record
|
|
256
|
+
:assumed
|
|
257
|
+
rescue Net::SMTPServerBusy => e
|
|
258
|
+
record_error(e, retryable: true) if record
|
|
240
259
|
:assumed
|
|
241
260
|
rescue StandardError => e
|
|
242
261
|
log "SMTP probe failed for #{recipient} on #{mail_server}:#{port}: #{e.message}"
|
|
243
262
|
:connection_failed
|
|
244
263
|
end
|
|
245
264
|
|
|
246
|
-
def classify_fatal(message)
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
msg.include?("mailbox unavailable")
|
|
265
|
+
def classify_fatal(message, record: true)
|
|
266
|
+
code = extract_code(message)
|
|
267
|
+
case code&.[](0)
|
|
268
|
+
when "5"
|
|
251
269
|
:invalid
|
|
252
|
-
|
|
270
|
+
when "4"
|
|
271
|
+
@smtp_retryable = true if record
|
|
253
272
|
:assumed
|
|
254
273
|
else
|
|
274
|
+
if record && message.to_s.downcase.include?("temporary")
|
|
275
|
+
@smtp_retryable = true
|
|
276
|
+
end
|
|
255
277
|
:assumed
|
|
256
278
|
end
|
|
257
279
|
end
|
|
258
280
|
|
|
281
|
+
def record_response(code, message)
|
|
282
|
+
@smtp_response = { code: code.to_s, message: message.to_s.strip }
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def record_error(err, retryable: false)
|
|
286
|
+
code = extract_code(err.message)
|
|
287
|
+
@smtp_response = { code: code, message: err.message.to_s.strip }
|
|
288
|
+
@smtp_retryable = true if retryable || code&.start_with?("4")
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def record_transient(message)
|
|
292
|
+
@smtp_response = { code: nil, message: message.to_s.strip }
|
|
293
|
+
@smtp_retryable = true
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def extract_code(message)
|
|
297
|
+
message.to_s[/\b([45]\d{2})\b/, 1]
|
|
298
|
+
end
|
|
299
|
+
|
|
259
300
|
def check_catch_all(mail_server)
|
|
260
301
|
domain = extract_domain
|
|
261
302
|
return unless domain
|
|
262
303
|
|
|
263
304
|
fake = "definitely-does-not-exist-#{rand(10**10)}@#{domain}"
|
|
264
305
|
result = SMTP_PORTS.each do |port|
|
|
265
|
-
outcome = attempt_rcpt(mail_server, port, fake)
|
|
306
|
+
outcome = attempt_rcpt(mail_server, port, fake, record: false)
|
|
266
307
|
break :accepted if outcome == :verified
|
|
267
308
|
break :rejected if outcome == :invalid
|
|
268
309
|
end
|
|
@@ -367,6 +408,11 @@ module Deliverable
|
|
|
367
408
|
"valid"
|
|
368
409
|
end
|
|
369
410
|
|
|
411
|
+
def retryable?(cls)
|
|
412
|
+
return false unless cls == "risky"
|
|
413
|
+
@smtp_retryable || @details[:dns_failed_assumption]
|
|
414
|
+
end
|
|
415
|
+
|
|
370
416
|
def log(message)
|
|
371
417
|
@logger&.warn(message)
|
|
372
418
|
end
|
data/lib/deliverable/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: deliverable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Erik Strömberg
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 1980-01-
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: valid_email2
|