truemail 1.6.1 → 1.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +1 -1
- data/.reek.yml +5 -0
- data/.rubocop.yml +177 -6
- data/CHANGELOG.md +241 -1
- data/Gemfile.lock +55 -68
- data/LICENSE.txt +1 -1
- data/README.md +237 -90
- data/lib/truemail.rb +1 -1
- data/lib/truemail/audit/base.rb +8 -0
- data/lib/truemail/audit/dns.rb +26 -0
- data/lib/truemail/audit/ip.rb +28 -0
- data/lib/truemail/audit/ptr.rb +8 -37
- data/lib/truemail/auditor.rb +7 -5
- data/lib/truemail/configuration.rb +10 -3
- data/lib/truemail/core.rb +27 -22
- data/lib/truemail/executor.rb +11 -0
- data/lib/truemail/log/serializer/auditor_json.rb +25 -0
- data/lib/truemail/log/serializer/base.rb +17 -41
- data/lib/truemail/log/serializer/validator_base.rb +45 -0
- data/lib/truemail/log/serializer/{json.rb → validator_json.rb} +1 -1
- data/lib/truemail/log/serializer/{text.rb → validator_text.rb} +2 -2
- data/lib/truemail/logger.rb +1 -1
- data/lib/truemail/validate/mx.rb +1 -0
- data/lib/truemail/validator.rb +4 -4
- data/lib/truemail/version.rb +1 -1
- data/truemail.gemspec +17 -9
- metadata +48 -32
data/Gemfile.lock
CHANGED
@@ -1,53 +1,40 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
truemail (1.
|
4
|
+
truemail (1.9.1)
|
5
5
|
simpleidn (~> 0.1.1)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
ast (2.4.
|
11
|
-
|
12
|
-
descendants_tracker (~> 0.0.4)
|
13
|
-
ice_nine (~> 0.11.0)
|
14
|
-
thread_safe (~> 0.3, >= 0.3.1)
|
15
|
-
bundler-audit (0.6.1)
|
10
|
+
ast (2.4.1)
|
11
|
+
bundler-audit (0.7.0.1)
|
16
12
|
bundler (>= 1.2.0, < 3)
|
17
|
-
thor (
|
18
|
-
byebug (11.1.
|
19
|
-
childprocess (
|
20
|
-
|
21
|
-
virtus (~> 1.0)
|
22
|
-
coderay (1.1.2)
|
23
|
-
coercible (1.0.0)
|
24
|
-
descendants_tracker (~> 0.0.1)
|
13
|
+
thor (>= 0.18, < 2)
|
14
|
+
byebug (11.1.3)
|
15
|
+
childprocess (4.0.0)
|
16
|
+
coderay (1.1.3)
|
25
17
|
colorize (0.8.1)
|
26
|
-
|
27
|
-
thread_safe (~> 0.3, >= 0.3.1)
|
28
|
-
diff-lcs (1.3)
|
18
|
+
diff-lcs (1.4.4)
|
29
19
|
docile (1.3.2)
|
30
|
-
|
31
|
-
fasterer (0.8.2)
|
20
|
+
fasterer (0.8.3)
|
32
21
|
colorize (~> 0.7)
|
33
22
|
ruby_parser (>= 3.14.1)
|
34
|
-
ffaker (2.
|
35
|
-
|
36
|
-
|
37
|
-
jaro_winkler (1.5.4)
|
38
|
-
json (2.3.0)
|
23
|
+
ffaker (2.17.0)
|
24
|
+
iniparse (1.5.0)
|
25
|
+
json (2.3.1)
|
39
26
|
json_matchers (0.11.1)
|
40
27
|
json_schema
|
41
|
-
json_schema (0.20.
|
28
|
+
json_schema (0.20.9)
|
42
29
|
kwalify (0.7.2)
|
43
30
|
method_source (1.0.0)
|
44
|
-
overcommit (0.
|
45
|
-
childprocess (>= 0.6.3, <
|
31
|
+
overcommit (0.55.0)
|
32
|
+
childprocess (>= 0.6.3, < 5)
|
46
33
|
iniparse (~> 1.4)
|
47
|
-
parallel (1.19.
|
48
|
-
parser (2.7.
|
49
|
-
ast (~> 2.4.
|
50
|
-
pry (0.13.
|
34
|
+
parallel (1.19.2)
|
35
|
+
parser (2.7.1.4)
|
36
|
+
ast (~> 2.4.1)
|
37
|
+
pry (0.13.1)
|
51
38
|
coderay (~> 1.1)
|
52
39
|
method_source (~> 1.0)
|
53
40
|
pry-byebug (3.9.0)
|
@@ -56,40 +43,46 @@ GEM
|
|
56
43
|
psych (3.1.0)
|
57
44
|
rainbow (3.0.0)
|
58
45
|
rake (13.0.1)
|
59
|
-
reek (
|
60
|
-
codeclimate-engine-rb (~> 0.4.0)
|
46
|
+
reek (6.0.1)
|
61
47
|
kwalify (~> 0.7.0)
|
62
48
|
parser (>= 2.5.0.0, < 2.8, != 2.5.1.1)
|
63
49
|
psych (~> 3.1.0)
|
64
50
|
rainbow (>= 2.0, < 4.0)
|
51
|
+
regexp_parser (1.8.0)
|
52
|
+
rexml (3.2.4)
|
65
53
|
rspec (3.9.0)
|
66
54
|
rspec-core (~> 3.9.0)
|
67
55
|
rspec-expectations (~> 3.9.0)
|
68
56
|
rspec-mocks (~> 3.9.0)
|
69
|
-
rspec-core (3.9.
|
70
|
-
rspec-support (~> 3.9.
|
71
|
-
rspec-expectations (3.9.
|
57
|
+
rspec-core (3.9.2)
|
58
|
+
rspec-support (~> 3.9.3)
|
59
|
+
rspec-expectations (3.9.2)
|
72
60
|
diff-lcs (>= 1.2.0, < 2.0)
|
73
61
|
rspec-support (~> 3.9.0)
|
74
62
|
rspec-mocks (3.9.1)
|
75
63
|
diff-lcs (>= 1.2.0, < 2.0)
|
76
64
|
rspec-support (~> 3.9.0)
|
77
|
-
rspec-support (3.9.
|
78
|
-
rubocop (0.
|
79
|
-
jaro_winkler (~> 1.5.1)
|
65
|
+
rspec-support (3.9.3)
|
66
|
+
rubocop (0.91.0)
|
80
67
|
parallel (~> 1.10)
|
81
|
-
parser (>= 2.7.
|
68
|
+
parser (>= 2.7.1.1)
|
82
69
|
rainbow (>= 2.2.2, < 4.0)
|
70
|
+
regexp_parser (>= 1.7)
|
71
|
+
rexml
|
72
|
+
rubocop-ast (>= 0.4.0, < 1.0)
|
83
73
|
ruby-progressbar (~> 1.7)
|
84
|
-
unicode-display_width (>= 1.4.0, <
|
85
|
-
rubocop-
|
86
|
-
|
87
|
-
rubocop-
|
88
|
-
rubocop (>= 0.
|
74
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
75
|
+
rubocop-ast (0.4.2)
|
76
|
+
parser (>= 2.7.1.4)
|
77
|
+
rubocop-performance (1.8.1)
|
78
|
+
rubocop (>= 0.87.0)
|
79
|
+
rubocop-ast (>= 0.4.0)
|
80
|
+
rubocop-rspec (1.43.2)
|
81
|
+
rubocop (~> 0.87)
|
89
82
|
ruby-progressbar (1.10.1)
|
90
|
-
ruby_parser (3.
|
83
|
+
ruby_parser (3.15.0)
|
91
84
|
sexp_processor (~> 4.9)
|
92
|
-
sexp_processor (4.
|
85
|
+
sexp_processor (4.15.1)
|
93
86
|
simplecov (0.17.1)
|
94
87
|
docile (~> 1.1)
|
95
88
|
json (>= 1.8, < 3)
|
@@ -97,41 +90,35 @@ GEM
|
|
97
90
|
simplecov-html (0.10.2)
|
98
91
|
simpleidn (0.1.1)
|
99
92
|
unf (~> 0.1.4)
|
100
|
-
thor (0.
|
101
|
-
|
102
|
-
|
103
|
-
rspec (~> 3.0)
|
93
|
+
thor (1.0.1)
|
94
|
+
truemail-rspec (0.2.1)
|
95
|
+
rspec (~> 3.9)
|
104
96
|
truemail (~> 1.4, >= 1.4.1)
|
105
97
|
unf (0.1.4)
|
106
98
|
unf_ext
|
107
|
-
unf_ext (0.0.7.
|
108
|
-
unicode-display_width (1.
|
109
|
-
virtus (1.0.5)
|
110
|
-
axiom-types (~> 0.1)
|
111
|
-
coercible (~> 1.0)
|
112
|
-
descendants_tracker (~> 0.0, >= 0.0.3)
|
113
|
-
equalizer (~> 0.0, >= 0.0.9)
|
99
|
+
unf_ext (0.0.7.7)
|
100
|
+
unicode-display_width (1.7.0)
|
114
101
|
|
115
102
|
PLATFORMS
|
116
103
|
ruby
|
117
104
|
|
118
105
|
DEPENDENCIES
|
119
106
|
bundler (~> 1.16)
|
120
|
-
bundler-audit (~> 0.
|
121
|
-
fasterer (~> 0.8.
|
122
|
-
ffaker (~> 2.
|
107
|
+
bundler-audit (~> 0.7.0.1)
|
108
|
+
fasterer (~> 0.8.3)
|
109
|
+
ffaker (~> 2.17)
|
123
110
|
json_matchers (~> 0.11.1)
|
124
|
-
overcommit (~> 0.
|
111
|
+
overcommit (~> 0.55.0)
|
125
112
|
pry-byebug (~> 3.9)
|
126
113
|
rake (~> 13.0, >= 13.0.1)
|
127
|
-
reek (~>
|
114
|
+
reek (~> 6.0, >= 6.0.1)
|
128
115
|
rspec (~> 3.9)
|
129
|
-
rubocop (~> 0.
|
130
|
-
rubocop-performance (~> 1.
|
131
|
-
rubocop-rspec (~> 1.
|
116
|
+
rubocop (~> 0.91.0)
|
117
|
+
rubocop-performance (~> 1.8, >= 1.8.1)
|
118
|
+
rubocop-rspec (~> 1.43, >= 1.43.2)
|
132
119
|
simplecov (~> 0.17.1)
|
133
120
|
truemail!
|
134
|
-
truemail-rspec (~> 0.
|
121
|
+
truemail-rspec (~> 0.2.1)
|
135
122
|
|
136
123
|
BUNDLED WITH
|
137
124
|
1.16.6
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2019 Vladislav Trotsenko
|
3
|
+
Copyright (c) 2019-2020 Vladislav Trotsenko
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -1,13 +1,16 @@
|
|
1
|
-
|
1
|
+
![Truemail - configurable framework agnostic plain Ruby email validator](https://truemail-rb.org/assets/images/truemail_logo.png)
|
2
2
|
|
3
3
|
[![Maintainability](https://api.codeclimate.com/v1/badges/657aa241399927dcd2e2/maintainability)](https://codeclimate.com/github/rubygarage/truemail/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/657aa241399927dcd2e2/test_coverage)](https://codeclimate.com/github/rubygarage/truemail/test_coverage) [![CircleCI](https://circleci.com/gh/rubygarage/truemail/tree/master.svg?style=svg)](https://circleci.com/gh/rubygarage/truemail/tree/master) [![Gem Version](https://badge.fury.io/rb/truemail.svg)](https://badge.fury.io/rb/truemail) [![Downloads](https://img.shields.io/gem/dt/truemail.svg?colorA=004d99&colorB=0073e6)](https://rubygems.org/gems/truemail) [![Gitter](https://badges.gitter.im/truemail-rb/community.svg)](https://gitter.im/truemail-rb/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md)
|
4
4
|
|
5
5
|
Configurable framework agnostic plain Ruby email validator. Verify email via Regex, DNS and SMTP. Be sure that email address valid and exists.
|
6
6
|
|
7
|
+
> Actual and maintainable documentation :books: for developers is living [here](https://truemail-rb.org/truemail-gem).
|
8
|
+
|
7
9
|
## Table of Contents
|
8
10
|
|
9
11
|
- [Synopsis](#synopsis)
|
10
12
|
- [Features](#features)
|
13
|
+
- [Requirements](#requirements)
|
11
14
|
- [Installation](#installation)
|
12
15
|
- [Usage](#usage)
|
13
16
|
- [Configuration features](#configuration-features)
|
@@ -26,14 +29,21 @@ Configurable framework agnostic plain Ruby email validator. Verify email via Reg
|
|
26
29
|
- [With default regex pattern](#with-default-regex-pattern)
|
27
30
|
- [With custom regex pattern](#with-custom-regex-pattern)
|
28
31
|
- [DNS (MX) validation](#mx-validation)
|
32
|
+
- [RFC MX lookup flow](#rfc-mx-lookup-flow)
|
33
|
+
- [Not RFC MX lookup flow](#not-rfc-mx-lookup-flow)
|
29
34
|
- [SMTP validation](#smtp-validation)
|
30
35
|
- [SMTP safe check disabled](#smtp-safe-check-disabled)
|
31
36
|
- [SMTP safe check enabled](#smtp-safe-check-enabled)
|
32
|
-
- [Event logger](#event-logger)
|
33
|
-
- [Available tracking events](#available-tracking-events)
|
34
|
-
- [JSON serializer](#json-serializer)
|
35
37
|
- [Host audit features](#host-audit-features)
|
38
|
+
- [IP audit](#ip-audit)
|
39
|
+
- [DNS audit](#dns-audit)
|
36
40
|
- [PTR audit](#ptr-audit)
|
41
|
+
- [Example of using](#example-of-using)
|
42
|
+
- [Event logger](#event-logger)
|
43
|
+
- [Available tracking events](#available-tracking-events)
|
44
|
+
- [JSON serializers](#json-serializers)
|
45
|
+
- [Auditor JSON serializer](#auditor-json-serializer)
|
46
|
+
- [Validator JSON serializer](#validator-json-serializer)
|
37
47
|
- [Truemail helpers](#truemail-helpers)
|
38
48
|
- [.valid?](#valid)
|
39
49
|
- [#as_json](#as_json)
|
@@ -45,11 +55,10 @@ Configurable framework agnostic plain Ruby email validator. Verify email via Reg
|
|
45
55
|
- [Credits](#credits)
|
46
56
|
- [Versioning](#versioning)
|
47
57
|
- [Changelog](CHANGELOG.md)
|
48
|
-
- [Wiki](https://github.com/rubygarage/truemail/wiki)
|
49
58
|
|
50
59
|
## Synopsis
|
51
60
|
|
52
|
-
Email validation is a tricky thing. There are a number of different ways to validate an email address and all mechanisms must conform with the best practices and provide proper validation.
|
61
|
+
Email validation is a tricky thing. There are a number of different ways to validate an email address and all mechanisms must conform with the best practices and provide proper validation. The Truemail gem helps you validate emails via regex pattern, presence of DNS records, and real existence of email account on a current email server.
|
53
62
|
|
54
63
|
**Syntax Checking**: Checks the email addresses via regex pattern.
|
55
64
|
|
@@ -67,7 +76,12 @@ Also Truemail gem allows performing an audit of the host in which runs.
|
|
67
76
|
- Whitelist/blacklist validation layers
|
68
77
|
- Simple SMTP debugger
|
69
78
|
- Event logger
|
70
|
-
-
|
79
|
+
- Host auditor tools (helps to detect common host problems interfering to proper email verification)
|
80
|
+
- JSON serializers
|
81
|
+
|
82
|
+
## Requirements
|
83
|
+
|
84
|
+
Ruby MRI 2.5.0+
|
71
85
|
|
72
86
|
## Installation
|
73
87
|
|
@@ -79,11 +93,15 @@ gem 'truemail'
|
|
79
93
|
|
80
94
|
And then execute:
|
81
95
|
|
82
|
-
|
96
|
+
```bash
|
97
|
+
bundle
|
98
|
+
```
|
83
99
|
|
84
100
|
Or install it yourself as:
|
85
101
|
|
86
|
-
|
102
|
+
```bash
|
103
|
+
gem install truemail
|
104
|
+
```
|
87
105
|
|
88
106
|
## Usage
|
89
107
|
|
@@ -168,6 +186,11 @@ Truemail.configure do |config|
|
|
168
186
|
# It is equal to empty array by default.
|
169
187
|
config.blacklisted_domains = ['somedomain1.com', 'somedomain2.com']
|
170
188
|
|
189
|
+
# Optional parameter. This option will provide to use not RFC MX lookup flow.
|
190
|
+
# It means that MX and Null MX records will be cheked on the DNS validation layer only.
|
191
|
+
# By default this option is disabled.
|
192
|
+
config.not_rfc_mx_lookup_flow = true
|
193
|
+
|
171
194
|
# Optional parameter. This option will be parse bodies of SMTP errors. It will be helpful
|
172
195
|
# if SMTP server does not return an exact answer that the email does not exist
|
173
196
|
# By default this option is disabled, available for SMTP validation only.
|
@@ -198,7 +221,8 @@ Truemail.configuration
|
|
198
221
|
@whitelist_validation=true,
|
199
222
|
@blacklisted_domains=[],
|
200
223
|
@verifier_domain="somedomain.com",
|
201
|
-
@verifier_email="verifier@example.com"
|
224
|
+
@verifier_email="verifier@example.com",
|
225
|
+
@not_rfc_mx_lookup_flow=true,
|
202
226
|
@smtp_safe_check=true,
|
203
227
|
@logger=#<Truemail::Logger:0x0000557f837450b0
|
204
228
|
@event=:all, @file="/home/app/log/truemail.log", @stdout=true>>
|
@@ -227,6 +251,7 @@ Truemail.configuration
|
|
227
251
|
@blacklisted_domains=[],
|
228
252
|
@verifier_domain="somedomain.com",
|
229
253
|
@verifier_email="verifier@example.com",
|
254
|
+
@not_rfc_mx_lookup_flow=true,
|
230
255
|
@smtp_safe_check=true,
|
231
256
|
@logger=#<Truemail::Logger:0x0000557f837450b0
|
232
257
|
@event=:all, @file="/home/app/log/truemail.log", @stdout=true>>
|
@@ -309,6 +334,7 @@ Truemail.validate('email@white-domain.com')
|
|
309
334
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
310
335
|
@response_timeout=2,
|
311
336
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
337
|
+
@not_rfc_mx_lookup_flow=false,
|
312
338
|
@smtp_safe_check=false,
|
313
339
|
@validation_type_by_domain={"somedomain.com"=>:mx},
|
314
340
|
@verifier_domain="example.com",
|
@@ -355,6 +381,7 @@ Truemail.validate('email@white-domain.com', with: :regex)
|
|
355
381
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
356
382
|
@response_timeout=2,
|
357
383
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
384
|
+
@not_rfc_mx_lookup_flow=false,
|
358
385
|
@smtp_safe_check=false,
|
359
386
|
@validation_type_by_domain={},
|
360
387
|
@verifier_domain="example.com",
|
@@ -386,6 +413,7 @@ Truemail.validate('email@domain.com', with: :regex)
|
|
386
413
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
387
414
|
@response_timeout=2,
|
388
415
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
416
|
+
@not_rfc_mx_lookup_flow=false,
|
389
417
|
@smtp_safe_check=false,
|
390
418
|
@validation_type_by_domain={},
|
391
419
|
@verifier_domain="example.com",
|
@@ -419,6 +447,7 @@ Truemail.validate('email@black-domain.com')
|
|
419
447
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
420
448
|
@response_timeout=2,
|
421
449
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
450
|
+
@not_rfc_mx_lookup_flow=false,
|
422
451
|
@smtp_safe_check=false,
|
423
452
|
@validation_type_by_domain={},
|
424
453
|
@verifier_domain="example.com",
|
@@ -452,6 +481,7 @@ Truemail.validate('email@somedomain.com')
|
|
452
481
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
453
482
|
@response_timeout=2,
|
454
483
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
484
|
+
@not_rfc_mx_lookup_flow=false,
|
455
485
|
@smtp_safe_check=false,
|
456
486
|
@validation_type_by_domain={},
|
457
487
|
@verifier_domain="example.com",
|
@@ -501,6 +531,7 @@ Truemail.validate('email@example.com', with: :regex)
|
|
501
531
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
502
532
|
@response_timeout=2,
|
503
533
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
534
|
+
@not_rfc_mx_lookup_flow=false,
|
504
535
|
@smtp_safe_check=false,
|
505
536
|
@validation_type_by_domain={},
|
506
537
|
@verifier_domain="example.com",
|
@@ -542,6 +573,7 @@ Truemail.validate('email@example.com', with: :regex)
|
|
542
573
|
@email_pattern=/regex_pattern/,
|
543
574
|
@response_timeout=2,
|
544
575
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
576
|
+
@not_rfc_mx_lookup_flow=false,
|
545
577
|
@smtp_safe_check=false,
|
546
578
|
@validation_type_by_domain={},
|
547
579
|
@verifier_domain="example.com",
|
@@ -559,7 +591,54 @@ In fact it's DNS validation because it checks not MX records only. DNS validatio
|
|
559
591
|
[Whitelist/Blacklist] -> [Regex validation] -> [MX validation]
|
560
592
|
```
|
561
593
|
|
562
|
-
Please note, Truemail MX validator not performs strict compliance of the [RFC 5321](https://tools.ietf.org/html/rfc5321#section-5) standard for best validation outcome.
|
594
|
+
Please note, Truemail MX validator [not performs](https://github.com/rubygarage/truemail/issues/26) strict compliance of the [RFC 5321](https://tools.ietf.org/html/rfc5321#section-5) standard for best validation outcome.
|
595
|
+
|
596
|
+
##### RFC MX lookup flow
|
597
|
+
|
598
|
+
[Truemail MX lookup](https://slides.com/vladislavtrotsenko/truemail#/0/9) based on RFC 5321. It consists of 3 substeps: MX, CNAME and A record resolvers. The point of each resolver is attempt to extract the mail servers from email domain. If at least one server exists that validation is successful. Iteration is processing until resolver returns true.
|
599
|
+
|
600
|
+
Example of usage:
|
601
|
+
|
602
|
+
```ruby
|
603
|
+
require 'truemail'
|
604
|
+
|
605
|
+
Truemail.configure do |config|
|
606
|
+
config.verifier_email = 'verifier@example.com'
|
607
|
+
end
|
608
|
+
|
609
|
+
Truemail.validate('email@example.com', with: :mx)
|
610
|
+
|
611
|
+
=> #<Truemail::Validator:0x000055590c9c1c50
|
612
|
+
@result=
|
613
|
+
#<struct Truemail::Validator::Result
|
614
|
+
success=true,
|
615
|
+
email="email@example.com",
|
616
|
+
domain="example.com",
|
617
|
+
mail_servers=["127.0.1.1", "127.0.1.2"],
|
618
|
+
errors={},
|
619
|
+
smtp_debug=nil>,
|
620
|
+
configuration=
|
621
|
+
#<Truemail::Configuration:0x0000559b6e44af70
|
622
|
+
@blacklisted_domains=[],
|
623
|
+
@connection_attempts=2,
|
624
|
+
@connection_timeout=2,
|
625
|
+
@default_validation_type=:smtp,
|
626
|
+
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
627
|
+
@response_timeout=2,
|
628
|
+
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
629
|
+
@not_rfc_mx_lookup_flow=false,
|
630
|
+
@smtp_safe_check=false,
|
631
|
+
@validation_type_by_domain={},
|
632
|
+
@verifier_domain="example.com",
|
633
|
+
@verifier_email="verifier@example.com",
|
634
|
+
@whitelist_validation=false,
|
635
|
+
@whitelisted_domains=[]>,
|
636
|
+
@validation_type=:mx>
|
637
|
+
```
|
638
|
+
|
639
|
+
##### Not RFC MX lookup flow
|
640
|
+
|
641
|
+
Also Truemail has possibility to use not RFC MX lookup flow. It means that will be used only one MX resolver on the DNS validation layer. By default this option is disabled.
|
563
642
|
|
564
643
|
Example of usage:
|
565
644
|
|
@@ -568,6 +647,7 @@ require 'truemail'
|
|
568
647
|
|
569
648
|
Truemail.configure do |config|
|
570
649
|
config.verifier_email = 'verifier@example.com'
|
650
|
+
config.not_rfc_mx_lookup_flow = true
|
571
651
|
end
|
572
652
|
|
573
653
|
Truemail.validate('email@example.com', with: :mx)
|
@@ -590,6 +670,7 @@ Truemail.validate('email@example.com', with: :mx)
|
|
590
670
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
591
671
|
@response_timeout=2,
|
592
672
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
673
|
+
@not_rfc_mx_lookup_flow=true,
|
593
674
|
@smtp_safe_check=false,
|
594
675
|
@validation_type_by_domain={},
|
595
676
|
@verifier_domain="example.com",
|
@@ -611,7 +692,7 @@ If total count of MX servers is equal to one, ```Truemail::Smtp``` validator wil
|
|
611
692
|
|
612
693
|
By default, you don't need pass with-parameter to use it. Example of usage is specified below:
|
613
694
|
|
614
|
-
|
695
|
+
##### SMTP safe check disabled
|
615
696
|
|
616
697
|
With ```smtp_safe_check = false```
|
617
698
|
|
@@ -643,6 +724,7 @@ Truemail.validate('email@example.com')
|
|
643
724
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
644
725
|
@response_timeout=2,
|
645
726
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
727
|
+
@not_rfc_mx_lookup_flow=false,
|
646
728
|
@smtp_safe_check=false,
|
647
729
|
@validation_type_by_domain={},
|
648
730
|
@verifier_domain="example.com",
|
@@ -694,6 +776,7 @@ Truemail.validate('email@example.com')
|
|
694
776
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
695
777
|
@response_timeout=2,
|
696
778
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
779
|
+
@not_rfc_mx_lookup_flow=false,
|
697
780
|
@smtp_safe_check=false,
|
698
781
|
@validation_type_by_domain={},
|
699
782
|
@verifier_domain="example.com",
|
@@ -703,8 +786,7 @@ Truemail.validate('email@example.com')
|
|
703
786
|
@validation_type=:smtp>
|
704
787
|
```
|
705
788
|
|
706
|
-
|
707
|
-
###### SMTP safe check enabled
|
789
|
+
##### SMTP safe check enabled
|
708
790
|
|
709
791
|
With ```smtp_safe_check = true```
|
710
792
|
|
@@ -758,6 +840,7 @@ Truemail.validate('email@example.com')
|
|
758
840
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
759
841
|
@response_timeout=2,
|
760
842
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
843
|
+
@not_rfc_mx_lookup_flow=false,
|
761
844
|
@smtp_safe_check=false,
|
762
845
|
@validation_type_by_domain={},
|
763
846
|
@verifier_domain="example.com",
|
@@ -806,6 +889,7 @@ Truemail.validate('email@example.com')
|
|
806
889
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
807
890
|
@response_timeout=2,
|
808
891
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
892
|
+
@not_rfc_mx_lookup_flow=false,
|
809
893
|
@smtp_safe_check=false,
|
810
894
|
@validation_type_by_domain={},
|
811
895
|
@verifier_domain="example.com",
|
@@ -815,76 +899,33 @@ Truemail.validate('email@example.com')
|
|
815
899
|
@validation_type=:smtp>
|
816
900
|
```
|
817
901
|
|
818
|
-
###
|
819
|
-
|
820
|
-
Truemail gem allows to output tracking events to stdout/file or both of these. Please note, at least one of the outputs must exist. Tracking event by default is `:error`
|
821
|
-
|
822
|
-
```ruby
|
823
|
-
Truemail.configure do |config|
|
824
|
-
config.logger = { tracking_event: :all, stdout: true, log_absolute_path: '/home/app/log/truemail.log' }
|
825
|
-
end
|
826
|
-
```
|
902
|
+
### Host audit features
|
827
903
|
|
828
|
-
|
904
|
+
Truemail gem allows performing an audit of the host in which runs. It will help to detect common host problems interfering to proper email verification.
|
829
905
|
|
830
|
-
|
831
|
-
- `:unrecognized_error`, unrecognized errors only (when `smtp_safe_check = true` and SMTP server does not return an exact answer that the email does not exist)
|
832
|
-
- `:recognized_error`, recognized errors only
|
833
|
-
- `:error`, recognized and unrecognized errors only
|
906
|
+
#### IP audit
|
834
907
|
|
835
|
-
|
908
|
+
Checks is current Truemail host has proper internet connection and detects current host ip address.
|
836
909
|
|
837
|
-
|
910
|
+
#### DNS audit
|
838
911
|
|
839
|
-
|
840
|
-
Truemail::Log::Serializer::Json.call(Truemail.validate('nonexistent_email@bestweb.com.ua'))
|
841
|
-
|
842
|
-
=>
|
843
|
-
# Serialized Truemail::Validator instance
|
844
|
-
{
|
845
|
-
"date": "2019-10-28 10:15:51 +0200",
|
846
|
-
"email": "nonexistent_email@bestweb.com.ua",
|
847
|
-
"validation_type": "smtp",
|
848
|
-
"success": false,
|
849
|
-
"errors": {
|
850
|
-
"smtp": "smtp error"
|
851
|
-
},
|
852
|
-
"smtp_debug": [
|
853
|
-
{
|
854
|
-
"mail_host": "213.180.193.89",
|
855
|
-
"port_opened": true,
|
856
|
-
"connection": true,
|
857
|
-
"errors": {
|
858
|
-
"rcptto": "550 5.7.1 No such user!\n"
|
859
|
-
}
|
860
|
-
}
|
861
|
-
],
|
862
|
-
"configuration": {
|
863
|
-
"validation_type_by_domain": null,
|
864
|
-
"whitelist_validation": false,
|
865
|
-
"whitelisted_domains": null,
|
866
|
-
"blacklisted_domains": null,
|
867
|
-
"smtp_safe_check": false,
|
868
|
-
"email_pattern": "default gem value",
|
869
|
-
"smtp_error_body_pattern": "default gem value"
|
870
|
-
}
|
871
|
-
}
|
872
|
-
```
|
873
|
-
|
874
|
-
### Host audit features
|
875
|
-
|
876
|
-
Truemail gem allows performing an audit of the host in which runs. Only PTR record audit performs for today.
|
912
|
+
Checks is verifier domain refer to current Truemail host IP address.
|
877
913
|
|
878
914
|
#### PTR audit
|
879
915
|
|
880
916
|
So what is a PTR record? A PTR record, or pointer record, enables someone to perform a reverse DNS lookup. This allows them to determine your domain name based on your IP address. Because generic domain names without a PTR are often associated with spammers, incoming mail servers identify email from hosts without PTR records as spam and you can't verify yours emails qualitatively.
|
881
917
|
|
918
|
+
Checks is PTR record exists for your Truemail host ip address exists and refers to current verifier domain.
|
919
|
+
|
920
|
+
#### Example of using
|
921
|
+
|
882
922
|
```ruby
|
883
923
|
Truemail.host_audit
|
884
924
|
# Everything is good
|
885
925
|
=> #<Truemail::Auditor:0x00005580df358828
|
886
926
|
@result=
|
887
927
|
#<struct Truemail::Auditor::Result
|
928
|
+
current_host_ip="127.0.0.1",
|
888
929
|
warnings={}>,
|
889
930
|
configuration=
|
890
931
|
#<Truemail::Configuration:0x00005615e86327a8
|
@@ -895,6 +936,7 @@ Truemail.host_audit
|
|
895
936
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
896
937
|
@response_timeout=2,
|
897
938
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
939
|
+
@not_rfc_mx_lookup_flow=false,
|
898
940
|
@smtp_safe_check=false,
|
899
941
|
@validation_type_by_domain={},
|
900
942
|
@verifier_domain="example.com",
|
@@ -902,12 +944,15 @@ Truemail.host_audit
|
|
902
944
|
@whitelist_validation=false,
|
903
945
|
@whitelisted_domains=[]>
|
904
946
|
|
905
|
-
# Has
|
947
|
+
# Has audit warnings
|
906
948
|
=> #<Truemail::Auditor:0x00005580df358828
|
907
949
|
@result=
|
908
950
|
#<struct Truemail::Auditor::Result
|
909
|
-
|
910
|
-
|
951
|
+
current_host_ip="127.0.0.1",
|
952
|
+
warnings={
|
953
|
+
:dns=>"A-record of verifier domain not refers to current host ip address",
|
954
|
+
:ptr=>"PTR-record does not reference to current verifier domain"
|
955
|
+
},
|
911
956
|
configuration=
|
912
957
|
#<Truemail::Configuration:0x00005615e86327a8
|
913
958
|
@blacklisted_domains=[],
|
@@ -917,6 +962,7 @@ Truemail.host_audit
|
|
917
962
|
@email_pattern=/(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|\.|\+]*)@((?i-mx:[\p{L}0-9]+([\-\.]{1}[\p{L}0-9]+)*\.[\p{L}]{2,63}))\z)/,
|
918
963
|
@response_timeout=2,
|
919
964
|
@smtp_error_body_pattern=/(?=.*550)(?=.*(user|account|customer|mailbox)).*/i,
|
965
|
+
@not_rfc_mx_lookup_flow=false,
|
920
966
|
@smtp_safe_check=false,
|
921
967
|
@validation_type_by_domain={},
|
922
968
|
@verifier_domain="example.com",
|
@@ -925,6 +971,91 @@ Truemail.host_audit
|
|
925
971
|
@whitelisted_domains=[]>
|
926
972
|
```
|
927
973
|
|
974
|
+
### Event logger
|
975
|
+
|
976
|
+
Truemail gem allows to output tracking events to stdout/file or both of these. Please note, at least one of the outputs must exist. Tracking event by default is `:error`
|
977
|
+
|
978
|
+
```ruby
|
979
|
+
Truemail.configure do |config|
|
980
|
+
config.logger = { tracking_event: :all, stdout: true, log_absolute_path: '/home/app/log/truemail.log' }
|
981
|
+
end
|
982
|
+
```
|
983
|
+
|
984
|
+
#### Available tracking events
|
985
|
+
|
986
|
+
- `:all`, all detected events including success validation cases
|
987
|
+
- `:unrecognized_error`, unrecognized errors only (when `smtp_safe_check = true` and SMTP server does not return an exact answer that the email does not exist)
|
988
|
+
- `:recognized_error`, recognized errors only
|
989
|
+
- `:error`, recognized and unrecognized errors only
|
990
|
+
|
991
|
+
### JSON serializers
|
992
|
+
|
993
|
+
Truemail has built in JSON serializers for `Truemail::Auditor` and `Truemail::Validator` instances, so you can represent your host audition or email validation result as json. Also you can use [#as_json](#as_json) helper for shortcuting.
|
994
|
+
|
995
|
+
#### Auditor JSON serializer
|
996
|
+
|
997
|
+
```ruby
|
998
|
+
Truemail::Log::Serializer::AuditorJson.call(Truemail.host_audit)
|
999
|
+
|
1000
|
+
=>
|
1001
|
+
# Serialized Truemail::Auditor instance
|
1002
|
+
{
|
1003
|
+
"date": "2020-08-31 22:33:43 +0300",
|
1004
|
+
"current_host_ip": "127.0.0.1",
|
1005
|
+
"warnings": {
|
1006
|
+
"dns": "A-record of verifier domain not refers to current host ip address", "ptr": "PTR-record does not reference to current verifier domain"
|
1007
|
+
},
|
1008
|
+
"configuration": {
|
1009
|
+
"validation_type_by_domain": null,
|
1010
|
+
"whitelist_validation": false,
|
1011
|
+
"whitelisted_domains": null,
|
1012
|
+
"blacklisted_domains": null,
|
1013
|
+
"not_rfc_mx_lookup_flow": false,
|
1014
|
+
"smtp_safe_check": false,
|
1015
|
+
"email_pattern": "default gem value",
|
1016
|
+
"smtp_error_body_pattern": "default gem value"
|
1017
|
+
}
|
1018
|
+
}
|
1019
|
+
```
|
1020
|
+
|
1021
|
+
#### Validator JSON serializer
|
1022
|
+
|
1023
|
+
```ruby
|
1024
|
+
Truemail::Log::Serializer::ValidatorJson.call(Truemail.validate('nonexistent_email@bestweb.com.ua'))
|
1025
|
+
|
1026
|
+
=>
|
1027
|
+
# Serialized Truemail::Validator instance
|
1028
|
+
{
|
1029
|
+
"date": "2019-10-28 10:15:51 +0200",
|
1030
|
+
"email": "nonexistent_email@bestweb.com.ua",
|
1031
|
+
"validation_type": "smtp",
|
1032
|
+
"success": false,
|
1033
|
+
"errors": {
|
1034
|
+
"smtp": "smtp error"
|
1035
|
+
},
|
1036
|
+
"smtp_debug": [
|
1037
|
+
{
|
1038
|
+
"mail_host": "213.180.193.89",
|
1039
|
+
"port_opened": true,
|
1040
|
+
"connection": true,
|
1041
|
+
"errors": {
|
1042
|
+
"rcptto": "550 5.7.1 No such user!\n"
|
1043
|
+
}
|
1044
|
+
}
|
1045
|
+
],
|
1046
|
+
"configuration": {
|
1047
|
+
"validation_type_by_domain": null,
|
1048
|
+
"whitelist_validation": false,
|
1049
|
+
"whitelisted_domains": null,
|
1050
|
+
"blacklisted_domains": null,
|
1051
|
+
"not_rfc_mx_lookup_flow": false,
|
1052
|
+
"smtp_safe_check": false,
|
1053
|
+
"email_pattern": "default gem value",
|
1054
|
+
"smtp_error_body_pattern": "default gem value"
|
1055
|
+
}
|
1056
|
+
}
|
1057
|
+
```
|
1058
|
+
|
928
1059
|
### Truemail helpers
|
929
1060
|
|
930
1061
|
#### .valid?
|
@@ -939,15 +1070,38 @@ Truemail.valid?('email@example.com')
|
|
939
1070
|
|
940
1071
|
#### #as_json
|
941
1072
|
|
942
|
-
You can use `#as_json` helper for represent `Truemail::Validator`
|
1073
|
+
You can use `#as_json` helper for represent `Truemail::Auditor` or `Truemail::Validator` instances as json. Under the hood it uses internal json `Truemail::Log::Serializer::AuditorJson` and `Truemail::Log::Serializer::ValidatorJson` [serializers](#json-serializers):
|
943
1074
|
|
944
1075
|
```ruby
|
1076
|
+
Truemail.host_audit.as_json
|
1077
|
+
|
1078
|
+
=>
|
1079
|
+
# Serialized Truemail::Auditor instance
|
1080
|
+
{
|
1081
|
+
"date": "2020-08-31 22:33:43 +0300",
|
1082
|
+
"current_host_ip": "127.0.0.1",
|
1083
|
+
"warnings": {
|
1084
|
+
"dns": "A-record of verifier domain not refers to current host ip address", "ptr": "PTR-record does not reference to current verifier domain"
|
1085
|
+
},
|
1086
|
+
"configuration": {
|
1087
|
+
"validation_type_by_domain": null,
|
1088
|
+
"whitelist_validation": false,
|
1089
|
+
"whitelisted_domains": null,
|
1090
|
+
"blacklisted_domains": null,
|
1091
|
+
"not_rfc_mx_lookup_flow": false,
|
1092
|
+
"smtp_safe_check": false,
|
1093
|
+
"email_pattern": "default gem value",
|
1094
|
+
"smtp_error_body_pattern": "default gem value"
|
1095
|
+
}
|
1096
|
+
}
|
1097
|
+
|
1098
|
+
|
945
1099
|
Truemail.validate('nonexistent_email@bestweb.com.ua').as_json
|
946
1100
|
|
947
1101
|
=>
|
948
1102
|
# Serialized Truemail::Validator instance
|
949
1103
|
{
|
950
|
-
"date": "2020-
|
1104
|
+
"date": "2020-05-10 10:00:00 +0200",
|
951
1105
|
"email": "nonexistent_email@bestweb.com.ua",
|
952
1106
|
"validation_type": "smtp",
|
953
1107
|
"success": false,
|
@@ -969,6 +1123,7 @@ Truemail.validate('nonexistent_email@bestweb.com.ua').as_json
|
|
969
1123
|
"whitelist_validation": false,
|
970
1124
|
"whitelisted_domains": null,
|
971
1125
|
"blacklisted_domains": null,
|
1126
|
+
"not_rfc_mx_lookup_flow": false,
|
972
1127
|
"smtp_safe_check": false,
|
973
1128
|
"email_pattern": "default gem value",
|
974
1129
|
"smtp_error_body_pattern": "default gem value"
|
@@ -1008,21 +1163,18 @@ end
|
|
1008
1163
|
```
|
1009
1164
|
|
1010
1165
|
---
|
1011
|
-
## Truemail family
|
1012
|
-
|
1013
|
-
All Truemail extensions: https://github.com/truemail-rb
|
1014
|
-
|
1015
|
-
### truemail server
|
1016
1166
|
|
1017
|
-
|
1018
|
-
|
1019
|
-
### truemail-rack-docker-image
|
1020
|
-
|
1021
|
-
Lightweight rack based web API dockerized image :whale: of [Truemail server](https://github.com/truemail-rb/truemail-rack) hosted on [dockerhub](https://hub.docker.com/r/truemail/truemail-rack), https://github.com/truemail-rb/truemail-rack-docker-image
|
1167
|
+
## Truemail family
|
1022
1168
|
|
1023
|
-
|
1169
|
+
All Truemail solutions: https://truemail-rb.org
|
1024
1170
|
|
1025
|
-
|
1171
|
+
| Name | Type | Description |
|
1172
|
+
| --- | --- | --- |
|
1173
|
+
| [truemail server](https://github.com/truemail-rb/truemail-rack) | ruby app | Lightweight rack based web API wrapper for Truemail gem |
|
1174
|
+
| [truemail-rack-docker](https://github.com/truemail-rb/truemail-rack-docker-image) | docker image | Lightweight rack based web API [dockerized image](https://hub.docker.com/r/truemail/truemail-rack) :whale: of Truemail server |
|
1175
|
+
| [truemail-ruby-client](https://github.com/truemail-rb/truemail-ruby-client) | ruby gem | Web API Ruby client for Truemail Server |
|
1176
|
+
| [truemail-crystal-client](https://github.com/truemail-rb/truemail-crystal-client) | crystal shard | Web API Crystal client for Truemail Server |
|
1177
|
+
| [truemail-rspec](https://github.com/truemail-rb/truemail-rspec) | ruby gem | Truemail configuration, auditor and validator RSpec helpers |
|
1026
1178
|
|
1027
1179
|
## Contributing
|
1028
1180
|
|
@@ -1044,8 +1196,3 @@ Everyone interacting in the Truemail project’s codebases, issue trackers, chat
|
|
1044
1196
|
## Versioning
|
1045
1197
|
|
1046
1198
|
Truemail uses [Semantic Versioning 2.0.0](https://semver.org)
|
1047
|
-
|
1048
|
-
---
|
1049
|
-
<a href="https://rubygarage.org/"><img src="https://rubygarage.s3.amazonaws.com/assets/assets/rg_color_logo_horizontal-919afc51a81d2e40cb6a0b43ee832e3fcd49669d06785156d2d16fd0d799f89e.png" alt="RubyGarage Logo" width="415" height="128"></a>
|
1050
|
-
|
1051
|
-
RubyGarage is a leading software development and consulting company in Eastern Europe. Our main expertise includes Ruby and Ruby on Rails, but we successfully employ other technologies to deliver the best results to our clients. [Check out our portfolio](https://rubygarage.org/portfolio) for even more exciting works!
|