tuktuk 0.6.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9b23aad601ecb20c8e9f2a61ca0c5d8740fe08fb
4
- data.tar.gz: ccd26989dcc9cf8ea0cd124bc117cd8aa4ebe337
2
+ SHA256:
3
+ metadata.gz: 94130469fa773ef26742e1811c40e3dcf6898acf537439470c72c1341cf555c8
4
+ data.tar.gz: 215936b8fe1b176a642f1c77f04f282f6df1f31e66381b62db09963c81a16044
5
5
  SHA512:
6
- metadata.gz: 573938ae9b56c2fea03e8cf19ae133b298e146232813b022ee2cc17e275e68cf98a353e4863eec720da3a898a7e78d3ba59926963db846bc02f215cb87eaf92f
7
- data.tar.gz: 0fb02b910a04a3f0e2472e7e65264add4eed11cc12a46f68b3a1adf820f9ff615392d24a65831da98aafa95f0a909d828f68454192111706aad905ee5fc487a1
6
+ metadata.gz: 7f8907098711e0e7447165913bf300f956cf72b192f9458f7d6e4b7a2560f2aef12bbe784c9b27cd017d294cb4d4e005a61e595f816148012ee48ce015e51639
7
+ data.tar.gz: 9f08addb37fd31f1dd1226377746053e424c3386f6a51fcd1e2eff28beb4e786fb603a259877112bd3e23474395780a1a5ec394dc27650058183ac97bce7369d
@@ -0,0 +1,19 @@
1
+ name: Ruby
2
+ on: [push]
3
+
4
+ jobs:
5
+ build:
6
+ runs-on: ubuntu-latest
7
+
8
+ steps:
9
+ - uses: actions/checkout@v1
10
+ - name: Set up Ruby 2.6
11
+ uses: actions/setup-ruby@v1
12
+ with:
13
+ ruby-version: 2.6.x
14
+ - name: Build and test with Rake
15
+ run: |
16
+ gem install bundler
17
+ gem install rspec
18
+ bundle install --jobs 4 --retry 3
19
+ rspec
data/README.md CHANGED
@@ -193,6 +193,49 @@ config.action_mailer.tuktuk_settings = {
193
193
  }
194
194
  ```
195
195
 
196
+ # Example SPK/DKIM/DMARC settings
197
+
198
+ If you're sending email from yoursite.com, the SPF record should be set for the APEX/root host, and look like this:
199
+
200
+ v=spf1 ip4:[ipv4_address] ip6:[ipv6_address] mx a include:[other_host] ~all
201
+
202
+ For example:
203
+
204
+ v=spf1 ip4:12.34.56.78 ip6:2600:3c05::f07c:92ff:fe48:b2fd mx a include:mailgun.org ~all
205
+
206
+ This tells the receiving server to accept email sent from a) the addresses explicitly mentioned (`ip4` and `ip6`),
207
+ b) from the hosts mentioned in the `include` statements, as well as c) the hosts listed as `MX` and `A` records for that domain.
208
+
209
+ As for DKIM, you should add two TXT records. The first is a simple, short one that goes under the `_domainkey` host,
210
+ and should contain the following:
211
+
212
+ t=y;o=~;
213
+
214
+ Then, a second DKIM record should be placed under `[selector]._domainkey` (e.g. `mailer._domainkey`), and should look like this:
215
+
216
+ k=rsa; p=MIIBIBANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA[...]DAQAB (public key)
217
+
218
+ And finally, your DMARC record goes under the `_dmarc` host, and goes like this:
219
+
220
+ v=DMARC1; p=none; rua=mailto:postmaster@yoursite.com; ruf=mailto:postmaster@yoursite.com
221
+
222
+ So, in summary:
223
+
224
+ (SPF) @.yoursite.com --> v=spf1 ip4:[ipv4_address] ip6:[ipv6_address] mx a include:[other_host] ~all
225
+ (DKIM1) _domainkey.yoursite.com --> t=y;o=~;
226
+ (DKIM2) [selector]._domainkey.yoursite.com --> k=rsa; p=MIIBIBANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA[...]DAQAB
227
+ (DMARC) _dmarc.yoursite.com --> v=DMARC1; p=none; rua=mailto:postmaster@yoursite.com; ruf=mailto:postmaster@yoursite.com
228
+
229
+ Now, to check wether your records are OK, you can use the `dig` command like follows:
230
+
231
+ dig yoursite.com TXT +short # should output the SPF record, under the root domain
232
+ dig mailer._domainkey.yoursite.com TXT +short # should output the DKIM record containing the key
233
+ dig _domainkey.yoursite.com TXT +short # should output the other (short) DKIM
234
+ dig _dmarc.yoursite.com TXT +short # should output the DMARC record
235
+
236
+ Remember you can query your DNS server directly with the `dig` command by adding `@name.server.com`
237
+ after the `dig` command (e.g. `dig @ns1.linode.com yoursite.com TXT`).
238
+
196
239
  # Contributions
197
240
 
198
241
  You're more than welcome. Send a pull request, including tests, and make sure you don't break anything. That's it.
@@ -20,6 +20,16 @@ DEFAULTS = {
20
20
  :log_to => nil # $stdout,
21
21
  }
22
22
 
23
+ # overwrite Net::SMTP#quit since the connection might have been closed
24
+ # before we got a chance to say goodbye. swallow the error in that case.
25
+ class Net::SMTP
26
+ def quit
27
+ getok('QUIT')
28
+ rescue EOFError => e
29
+ # nil
30
+ end
31
+ end
32
+
23
33
  module Tuktuk
24
34
 
25
35
  class << self
@@ -30,16 +40,19 @@ module Tuktuk
30
40
 
31
41
  def deliver(message, opts = {})
32
42
  # raise 'Please pass a valid message object.' unless message[:to]
43
+ bcc = opts.delete(:bcc) || []
44
+ bcc = [bcc] if bcc.is_a?(String)
45
+
33
46
  self.options = opts if opts.any?
34
47
  mail = Package.build(message)
35
- response = lookup_and_deliver(mail)
48
+ response = lookup_and_deliver(mail, bcc)
36
49
  return response, mail
37
50
  end
38
51
 
39
52
  # same as deliver but raises error. used by ActionMailer
40
- def deliver!(mail)
53
+ def deliver!(mail, opts = {})
41
54
  @logger = Rails.logger if defined?(Rails) and !config[:log_to]
42
- resp, email = deliver(mail)
55
+ resp, email = deliver(mail, opts)
43
56
  if resp.is_a?(Exception)
44
57
  raise resp
45
58
  else
@@ -89,7 +102,9 @@ module Tuktuk
89
102
  hash = {}
90
103
  array.each_with_index do |message, i|
91
104
  mail = Package.build(message, i)
92
- raise "Invalid destination count: #{mail.destinations.count}" if mail.destinations.count != 1
105
+ if mail.destinations.count != 1
106
+ raise ArgumentError, "Invalid destination count: #{mail.destinations.count}"
107
+ end
93
108
 
94
109
  if to = mail.destinations.first and domain = get_domain(to)
95
110
  domain = domain.downcase
@@ -109,7 +124,7 @@ module Tuktuk
109
124
  servers.any? && servers
110
125
  end
111
126
 
112
- def lookup_and_deliver(mail, attempt = 1)
127
+ def lookup_and_deliver(mail, bcc = [])
113
128
  if mail.destinations.empty?
114
129
  raise "No destinations found! You need to pass a :to field."
115
130
  end
@@ -127,9 +142,10 @@ module Tuktuk
127
142
  last_error = nil
128
143
  servers.each do |server|
129
144
  begin
130
- response = send_now(mail, server, to)
145
+ response = send_now(mail, server, to, bcc)
131
146
  break
132
147
  rescue Exception => e # explicitly rescue Exception so we catch Timeout:Error's too
148
+ logger.error "Error: #{e}"
133
149
  last_error = e
134
150
  end
135
151
  end
@@ -210,14 +226,15 @@ module Tuktuk
210
226
  responses
211
227
  end
212
228
 
213
- def send_now(mail, server, to)
229
+ def send_now(mail, server, to, bcc = [])
214
230
  logger.info "#{to} - Delivering email at #{server}..."
231
+ logger.info "Including these destinations: #{bcc.inspect}" if bcc && bcc.any?
215
232
  from = get_from(mail)
216
233
 
217
234
  response = nil
218
235
  socket = init_connection(server)
219
236
  socket.start(get_helo_domain(from), nil, nil, nil) do |smtp|
220
- response = smtp.send_message(get_raw_mail(mail), from, to)
237
+ response = smtp.send_message(get_raw_mail(mail), from, to, *bcc)
221
238
  logger.info "#{to} - [SENT] #{response.message.strip}"
222
239
  end
223
240
 
@@ -234,7 +251,7 @@ module Tuktuk
234
251
  begin
235
252
  resp = smtp.send_message(get_raw_mail(mail), get_from(mail), mail.to)
236
253
  smtp.send(:getok, 'RSET') if server['hotmail'] # fix for '503 Sender already specified'
237
- rescue Net::SMTPFatalError => e # error code 5xx, except for 500, like: 550 Mailbox not found
254
+ rescue Net::SMTPFatalError, Net::SMTPServerBusy => e # error code 5xx, except for 500, like: 550 Mailbox not found
238
255
  resp = Bounce.type(e)
239
256
  end
240
257
  responses[mail] = resp
@@ -243,8 +260,8 @@ module Tuktuk
243
260
  end
244
261
 
245
262
  responses
246
- rescue => e # SMTPServerBusy, SMTPSyntaxError, SMTPUnsupportedCommand, SMTPUnknownError (unexpected reply code)
247
- logger.error "[SERVER ERROR: #{server}] #{e.message}"
263
+ rescue Exception => e # SMTPServerBusy, SMTPSyntaxError, SMTPUnsupportedCommand, SMTPUnknownError (unexpected reply code)
264
+ logger.error "[SERVER ERROR: #{server}] #{e.class} -> #{e.message}"
248
265
  @last_error = e
249
266
  responses
250
267
  end
@@ -1,7 +1,7 @@
1
1
  module Tuktuk
2
2
  MAJOR = 0
3
- MINOR = 6
4
- PATCH = 3
3
+ MINOR = 8
4
+ PATCH = 0
5
5
 
6
6
  VERSION = [MAJOR, MINOR, PATCH].join('.')
7
7
  end
@@ -1,8 +1,142 @@
1
1
  require './lib/tuktuk/tuktuk'
2
2
  require 'rspec/mocks'
3
3
 
4
+ def email(attrs = {})
5
+ { :to => "user#{rand(1000)}@domain.com",
6
+ :from => 'me@company.com',
7
+ :subject => 'Test email',
8
+ :body => 'Hello world.'
9
+ }.merge(attrs)
10
+ end
11
+
4
12
  describe 'deliver' do
5
13
 
14
+ before(:each) do
15
+ @mock_smtp = double('Net::SMTP', enable_starttls_auto: true, :read_timeout= => true, :open_timeout= => true)
16
+ @mock_conn = double('SMTP Connection')
17
+ @mock_smtp.stub(:start).and_yield(@mock_conn)
18
+ @mock_resp = double('SMTP::Response', message: '250 OK')
19
+
20
+ Net::SMTP.stub(:new).and_return(@mock_smtp)
21
+ end
22
+
23
+ describe 'single recipient' do
24
+
25
+ describe 'when destination is valid (has MX servers)' do
26
+
27
+ before do
28
+ @servers = ['mx1.domain.com', 'mx2.domain.com', 'mx3.domain.com']
29
+ Tuktuk.stub(:smtp_servers_for_domain).and_return(@servers)
30
+ end
31
+
32
+ it 'sends message' do
33
+ msg = email
34
+ expect(@mock_conn).to receive(:send_message).with(String, msg[:from], msg[:to]).and_return(@mock_resp)
35
+ Tuktuk.deliver(msg)
36
+ end
37
+
38
+ describe 'and bcc is given' do
39
+
40
+ let(:bcc_email) { 'bcc@test.com' }
41
+
42
+ it 'includes it in destination list' do
43
+ msg = email
44
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to], bcc_email).and_return(@mock_resp)
45
+ Tuktuk.deliver(msg, bcc: [bcc_email])
46
+ end
47
+
48
+ it 'also works if not passed as array' do
49
+ msg = email
50
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to], bcc_email).and_return(@mock_resp)
51
+ Tuktuk.deliver(msg, bcc: bcc_email)
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ describe 'multiple recipients (string list)' do
61
+
62
+ describe 'when destination is valid (has MX servers)' do
63
+
64
+ before do
65
+ @servers = ['mx1.domain.com', 'mx2.domain.com', 'mx3.domain.com']
66
+ Tuktuk.stub(:smtp_servers_for_domain).and_return(@servers)
67
+ end
68
+
69
+ it 'sends message' do
70
+ msg = email(to: 'some@one.com, another@one.com')
71
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to].split(', ').first).and_return(@mock_resp)
72
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to].split(', ').last).and_return(@mock_resp)
73
+ Tuktuk.deliver(msg)
74
+ end
75
+
76
+ describe 'and bcc is given' do
77
+
78
+ let(:bcc_email) { 'bcc@test.com' }
79
+
80
+ it 'includes it in destination list' do
81
+ msg = email(to: 'some@one.com, another@one.com')
82
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to].split(', ').first, bcc_email).and_return(@mock_resp)
83
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to].split(', ').last, bcc_email).and_return(@mock_resp)
84
+ Tuktuk.deliver(msg, bcc: [bcc_email])
85
+ end
86
+
87
+ it 'also works if not passed as array' do
88
+ msg = email(to: 'some@one.com, another@one.com')
89
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to].split(', ').first, bcc_email).and_return(@mock_resp)
90
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to].split(', ').last, bcc_email).and_return(@mock_resp)
91
+ Tuktuk.deliver(msg, bcc: bcc_email)
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+
100
+ describe 'multiple recipients (array)' do
101
+
102
+ describe 'when destination is valid (has MX servers)' do
103
+
104
+ before do
105
+ @servers = ['mx1.domain.com', 'mx2.domain.com', 'mx3.domain.com']
106
+ Tuktuk.stub(:smtp_servers_for_domain).and_return(@servers)
107
+ end
108
+
109
+ it 'sends message' do
110
+ msg = email(to: ['some@one.com', 'another@one.com'])
111
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to].first).and_return(@mock_resp)
112
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to].last).and_return(@mock_resp)
113
+ Tuktuk.deliver(msg)
114
+ end
115
+
116
+ describe 'and bcc is given' do
117
+
118
+ let(:bcc_email) { 'bcc@test.com' }
119
+
120
+ it 'includes it in destination list' do
121
+ msg = email(to: ['some@one.com', 'another@one.com'])
122
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to].first, bcc_email).and_return(@mock_resp)
123
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to].last, bcc_email).and_return(@mock_resp)
124
+ Tuktuk.deliver(msg, bcc: [bcc_email])
125
+ end
126
+
127
+ it 'also works if not passed as array' do
128
+ msg = email(to: ['some@one.com', 'another@one.com'])
129
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to].first, bcc_email).and_return(@mock_resp)
130
+ expect(@mock_conn).to receive(:send_message).with(instance_of(String), msg[:from], msg[:to].last, bcc_email).and_return(@mock_resp)
131
+ Tuktuk.deliver(msg, bcc: bcc_email)
132
+ end
133
+
134
+ end
135
+
136
+ end
137
+
138
+ end
139
+
6
140
  end
7
141
 
8
142
  describe 'deliver many' do
@@ -17,7 +151,7 @@ describe 'deliver many' do
17
151
  it 'raises' do
18
152
  lambda do
19
153
  Tuktuk.deliver_many []
20
- end.should raise_error
154
+ end.should raise_error(ArgumentError)
21
155
  end
22
156
 
23
157
  end
@@ -26,8 +160,8 @@ describe 'deliver many' do
26
160
 
27
161
  it 'raises' do
28
162
  lambda do
29
- Tuktuk.deliver_many [ email, email(:to => 'one@user.com, two@user.com')]
30
- end.should raise_error
163
+ Tuktuk.deliver_many [ email, email(:to => 'one@user.com, two@user.com') ]
164
+ end.should raise_error(ArgumentError)
31
165
  end
32
166
 
33
167
  end
@@ -89,7 +223,7 @@ describe 'deliver many' do
89
223
  end
90
224
 
91
225
  it 'starts by delivering to first one' do
92
- Tuktuk.should_receive(:send_many_now).once.with('mx1.domain.com', [1]).and_return([[1,'ok']])
226
+ Tuktuk.should_receive(:send_many_now).once.with('mx1.domain.com', [1]).and_return([[1, 'ok']])
93
227
  Tuktuk.send(:lookup_and_deliver_by_domain, 'domain.com', [1])
94
228
  end
95
229
 
@@ -178,7 +312,7 @@ describe 'deliver many' do
178
312
  describe 'and other servers are down' do
179
313
 
180
314
  before do
181
- # TODO: for some reason the :init_connection on line 138 is affecting this
315
+ # TODO: for some reason the :init_connection on line 138 is affecting this
182
316
  # this test should pass when running on its own
183
317
  # Tuktuk.should_receive(:init_connection).once.with('mx1.domain.com').and_return(@mock_smtp)
184
318
  # Tuktuk.should_receive(:init_connection).once.with('mx2.domain.com').and_raise('Unable to connect.')
@@ -204,12 +338,4 @@ describe 'deliver many' do
204
338
 
205
339
  end
206
340
 
207
- def email(attrs = {})
208
- { :to => "user#{rand(1000)}@domain.com",
209
- :from => 'me@company.com',
210
- :subject => 'Test email',
211
- :body => 'Hello world.'
212
- }.merge(attrs)
213
- end
214
-
215
341
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tuktuk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomás Pollak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-12-03 00:00:00.000000000 Z
11
+ date: 2020-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -88,6 +88,7 @@ executables:
88
88
  extensions: []
89
89
  extra_rdoc_files: []
90
90
  files:
91
+ - ".github/workflows/ruby.yml"
91
92
  - ".gitignore"
92
93
  - Gemfile
93
94
  - README.md
@@ -123,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
124
  version: 1.3.6
124
125
  requirements: []
125
126
  rubyforge_project: tuktuk
126
- rubygems_version: 2.6.13
127
+ rubygems_version: 2.7.3
127
128
  signing_key:
128
129
  specification_version: 4
129
130
  summary: SMTP client for Ruby with DKIM support.