mail_room 0.8.1 → 0.9.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.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +31 -0
- data/CHANGELOG.md +6 -0
- data/README.md +60 -7
- data/lib/mail_room/arbitration/redis.rb +14 -9
- data/lib/mail_room/delivery/sidekiq.rb +17 -10
- data/lib/mail_room/version.rb +1 -1
- data/spec/lib/arbitration/redis_spec.rb +70 -6
- data/spec/lib/delivery/sidekiq_spec.rb +76 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee519fad16a2cfffc77987222062d15f59da6081
|
4
|
+
data.tar.gz: e403cac3cc18fe5a258d892dc69481535e01c4b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b001def60c902e4efab7b4708c655e6c07d2d1b4b94cd1d0a0703c91a987c05ba218834a9e711f377a2c38c524134de73a0b184d3e676b188cd077a1a3cf94c
|
7
|
+
data.tar.gz: 8df7ff1eb78ec3eb6f19d976c5b6fc3a884194daede0af990a4f2c74b2efcdd04b6c8dfd7f9dd2f8e7ee3fd156696ab5104062abfa6ea1f4d640d2cc2a9f7a53
|
data/.gitlab-ci.yml
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Cache gems in between builds
|
2
|
+
|
3
|
+
.test-template: &test
|
4
|
+
cache:
|
5
|
+
paths:
|
6
|
+
- vendor/ruby
|
7
|
+
script:
|
8
|
+
- bundle exec rspec spec
|
9
|
+
before_script:
|
10
|
+
- apt update && apt install -y libicu-dev
|
11
|
+
- ruby -v # Print out ruby version for debugging
|
12
|
+
# Uncomment next line if your rails app needs a JS runtime:
|
13
|
+
# - apt-get update -q && apt-get install nodejs -yqq
|
14
|
+
- gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image
|
15
|
+
- bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby
|
16
|
+
|
17
|
+
rspec-2.0:
|
18
|
+
image: "ruby:2.0"
|
19
|
+
<<: *test
|
20
|
+
|
21
|
+
rspec-2.1:
|
22
|
+
image: "ruby:2.1"
|
23
|
+
<<: *test
|
24
|
+
|
25
|
+
rspec-2.2:
|
26
|
+
image: "ruby:2.2"
|
27
|
+
<<: *test
|
28
|
+
|
29
|
+
rspec-2.3:
|
30
|
+
image: "ruby:2.3"
|
31
|
+
<<: *test
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -44,7 +44,7 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
|
|
44
44
|
:delivery_options:
|
45
45
|
:delivery_url: "http://localhost:3000/inbox"
|
46
46
|
:delivery_token: "abcdefg"
|
47
|
-
|
47
|
+
|
48
48
|
-
|
49
49
|
:email: "user2@gmail.com"
|
50
50
|
:password: "password"
|
@@ -76,6 +76,20 @@ You will also need to install `faraday` or `letter_opener` if you use the `postb
|
|
76
76
|
:delivery_options:
|
77
77
|
:redis_url: redis://localhost:6379
|
78
78
|
:worker: EmailReceiverWorker
|
79
|
+
-
|
80
|
+
:email: "user6@gmail.com"
|
81
|
+
:password: "password"
|
82
|
+
:name: "inbox"
|
83
|
+
:delivery_method: sidekiq
|
84
|
+
:delivery_options:
|
85
|
+
# When pointing to sentinel, follow this sintax for redis URLs:
|
86
|
+
# redis://:<password>@<master-name>/
|
87
|
+
:redis_url: redis://:password@my-redis-sentinel/
|
88
|
+
:sentinels:
|
89
|
+
-
|
90
|
+
:host: 127.0.0.1
|
91
|
+
:port: 26379
|
92
|
+
:worker: EmailReceiverWorker
|
79
93
|
```
|
80
94
|
|
81
95
|
## delivery_method ##
|
@@ -86,12 +100,12 @@ Requires `faraday` gem be installed.
|
|
86
100
|
|
87
101
|
*NOTE:* If you're using Ruby `>= 2.0`, you'll need to use Faraday from `>= 0.8.9`. Versions before this seem to have some weird behavior with `mail_room`.
|
88
102
|
|
89
|
-
The default delivery method, requires `delivery_url` and `delivery_token` in
|
103
|
+
The default delivery method, requires `delivery_url` and `delivery_token` in
|
90
104
|
configuration.
|
91
105
|
|
92
|
-
As the postback is essentially using your app as if it were an API endpoint,
|
93
|
-
you may need to disable forgery protection as you would with a JSON API. In
|
94
|
-
our case, the postback is plaintext, but the protection will still need to be
|
106
|
+
As the postback is essentially using your app as if it were an API endpoint,
|
107
|
+
you may need to disable forgery protection as you would with a JSON API. In
|
108
|
+
our case, the postback is plaintext, but the protection will still need to be
|
95
109
|
disabled.
|
96
110
|
|
97
111
|
### sidekiq ###
|
@@ -105,6 +119,8 @@ Configured with `:delivery_method: sidekiq`.
|
|
105
119
|
Delivery options:
|
106
120
|
- **redis_url**: The Redis server to connect with. Use the same Redis URL that's used to configure Sidekiq.
|
107
121
|
Required, defaults to `redis://localhost:6379`.
|
122
|
+
- **sentinels**: A list of sentinels servers used to provide HA to Redis. (see [Sentinel Support](#sentinel-support))
|
123
|
+
Optional.
|
108
124
|
- **namespace**: The Redis namespace Sidekiq works under. Use the same Redis namespace that's used to configure Sidekiq.
|
109
125
|
Optional.
|
110
126
|
- **queue**: The Sidekiq queue the job is pushed onto. Make sure Sidekiq actually reads off this queue.
|
@@ -224,20 +240,57 @@ When running multiple instances of MailRoom against a single mailbox, to try to
|
|
224
240
|
:delivery_options:
|
225
241
|
:delivery_url: "http://localhost:3000/inbox"
|
226
242
|
:delivery_token: "abcdefg"
|
227
|
-
|
243
|
+
|
228
244
|
:arbitration_method: redis
|
229
245
|
:arbitration_options:
|
230
246
|
# The Redis server to connect with. Defaults to redis://localhost:6379.
|
231
247
|
:redis_url: redis://redis.example.com:6379
|
232
|
-
# The Redis namespace to house the Redis keys under. Optional.
|
248
|
+
# The Redis namespace to house the Redis keys under. Optional.
|
233
249
|
:namespace: mail_room
|
250
|
+
-
|
251
|
+
:email: "user2@gmail.com"
|
252
|
+
:password: "password"
|
253
|
+
:name: "inbox"
|
254
|
+
:delivery_method: postback
|
255
|
+
:delivery_options:
|
256
|
+
:delivery_url: "http://localhost:3000/inbox"
|
257
|
+
:delivery_token: "abcdefg"
|
234
258
|
|
259
|
+
:arbitration_method: redis
|
260
|
+
:arbitration_options:
|
261
|
+
# When pointing to sentinel, follow this sintax for redis URLs:
|
262
|
+
# redis://:<password>@<master-name>/
|
263
|
+
:redis_url: redis://:password@my-redis-sentinel/
|
264
|
+
:sentinels:
|
265
|
+
-
|
266
|
+
:host: 127.0.0.1
|
267
|
+
:port: 26379
|
268
|
+
# The Redis namespace to house the Redis keys under. Optional.
|
269
|
+
:namespace: mail_room
|
235
270
|
```
|
236
271
|
|
237
272
|
**Note:** This will likely never be a _perfect_ system for preventing multiple deliveries of the same message, so I would advise checking the unique `message_id` if you are running in this situation.
|
238
273
|
|
239
274
|
**Note:** There are other scenarios for preventing duplication of messages at scale that _may_ be more appropriate in your particular setup. One such example is using multiple inboxes in reply-by-email situations. Another is to use labels and configure a different `SEARCH` command for each instance of MailRoom.
|
240
275
|
|
276
|
+
## Sentinel Support
|
277
|
+
|
278
|
+
Redis Sentinel provides high availability for Redis. Please read their [documentation](http://redis.io/topics/sentinel)
|
279
|
+
first, before enabling it with mail_room.
|
280
|
+
|
281
|
+
To connect to a Sentinel, you need to setup authentication to both sentinels and redis daemons first, and make sure
|
282
|
+
both are binding to a reachable IP address.
|
283
|
+
|
284
|
+
In mail_room, when you are connecting to a Sentinel, you have to inform the `master-name` and the `password` through
|
285
|
+
`redis_url` param, following this syntax:
|
286
|
+
|
287
|
+
```
|
288
|
+
redis://:<password>@<master-name>/
|
289
|
+
```
|
290
|
+
|
291
|
+
You also have to inform at least one pair of `host` and `port` for a sentinel in your cluster.
|
292
|
+
To have a minimum reliable setup, you need at least `3` sentinel nodes and `3` redis servers (1 master, 2 slaves).
|
293
|
+
|
241
294
|
## Contributing ##
|
242
295
|
|
243
296
|
1. Fork it
|
@@ -3,12 +3,13 @@ require "redis"
|
|
3
3
|
module MailRoom
|
4
4
|
module Arbitration
|
5
5
|
class Redis
|
6
|
-
Options = Struct.new(:redis_url, :namespace) do
|
6
|
+
Options = Struct.new(:redis_url, :namespace, :sentinels) do
|
7
7
|
def initialize(mailbox)
|
8
8
|
redis_url = mailbox.arbitration_options[:redis_url] || "redis://localhost:6379"
|
9
9
|
namespace = mailbox.arbitration_options[:namespace]
|
10
|
+
sentinels = mailbox.arbitration_options[:sentinels]
|
10
11
|
|
11
|
-
super(redis_url, namespace)
|
12
|
+
super(redis_url, namespace, sentinels)
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
@@ -25,12 +26,12 @@ module MailRoom
|
|
25
26
|
key = "delivered:#{uid}"
|
26
27
|
|
27
28
|
incr = nil
|
28
|
-
|
29
|
-
# At this point, `incr` is a future, which will get its value after
|
29
|
+
client.multi do |c|
|
30
|
+
# At this point, `incr` is a future, which will get its value after
|
30
31
|
# the MULTI command returns.
|
31
|
-
incr =
|
32
|
+
incr = c.incr(key)
|
32
33
|
|
33
|
-
|
34
|
+
c.expire(key, EXPIRATION)
|
34
35
|
end
|
35
36
|
|
36
37
|
# If INCR returns 1, that means the key didn't exist before, which means
|
@@ -42,9 +43,13 @@ module MailRoom
|
|
42
43
|
|
43
44
|
private
|
44
45
|
|
45
|
-
def
|
46
|
-
@
|
47
|
-
|
46
|
+
def client
|
47
|
+
@client ||= begin
|
48
|
+
sentinels = options.sentinels
|
49
|
+
redis_options = { url: options.redis_url }
|
50
|
+
redis_options[:sentinels] = sentinels if sentinels
|
51
|
+
|
52
|
+
redis = ::Redis.new(redis_options)
|
48
53
|
|
49
54
|
namespace = options.namespace
|
50
55
|
if namespace
|
@@ -8,14 +8,15 @@ module MailRoom
|
|
8
8
|
# Sidekiq Delivery method
|
9
9
|
# @author Douwe Maan
|
10
10
|
class Sidekiq
|
11
|
-
Options = Struct.new(:redis_url, :namespace, :queue, :worker) do
|
11
|
+
Options = Struct.new(:redis_url, :namespace, :sentinels, :queue, :worker) do
|
12
12
|
def initialize(mailbox)
|
13
13
|
redis_url = mailbox.delivery_options[:redis_url] || "redis://localhost:6379"
|
14
14
|
namespace = mailbox.delivery_options[:namespace]
|
15
|
+
sentinels = mailbox.delivery_options[:sentinels]
|
15
16
|
queue = mailbox.delivery_options[:queue] || "default"
|
16
17
|
worker = mailbox.delivery_options[:worker]
|
17
18
|
|
18
|
-
super(redis_url, namespace, queue, worker)
|
19
|
+
super(redis_url, namespace, sentinels, queue, worker)
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
@@ -40,14 +41,20 @@ module MailRoom
|
|
40
41
|
private
|
41
42
|
|
42
43
|
def client
|
43
|
-
client
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
Redis
|
49
|
-
|
50
|
-
|
44
|
+
@client ||= begin
|
45
|
+
sentinels = options.sentinels
|
46
|
+
redis_options = { url: options.redis_url }
|
47
|
+
redis_options[:sentinels] = sentinels if sentinels
|
48
|
+
|
49
|
+
redis = ::Redis.new(redis_options)
|
50
|
+
|
51
|
+
namespace = options.namespace
|
52
|
+
if namespace
|
53
|
+
require 'redis/namespace'
|
54
|
+
Redis::Namespace.new(namespace, redis: redis)
|
55
|
+
else
|
56
|
+
redis
|
57
|
+
end
|
51
58
|
end
|
52
59
|
end
|
53
60
|
|
data/lib/mail_room/version.rb
CHANGED
@@ -2,18 +2,18 @@ require 'spec_helper'
|
|
2
2
|
require 'mail_room/arbitration/redis'
|
3
3
|
|
4
4
|
describe MailRoom::Arbitration::Redis do
|
5
|
-
let(:mailbox) {
|
5
|
+
let(:mailbox) {
|
6
6
|
MailRoom::Mailbox.new(
|
7
7
|
arbitration_options: {
|
8
8
|
namespace: "mail_room"
|
9
9
|
}
|
10
|
-
)
|
10
|
+
)
|
11
11
|
}
|
12
12
|
let(:options) { described_class::Options.new(mailbox) }
|
13
13
|
subject { described_class.new(options) }
|
14
14
|
|
15
15
|
# Private, but we don't care.
|
16
|
-
let(:
|
16
|
+
let(:client) { subject.send(:client) }
|
17
17
|
|
18
18
|
describe '#deliver?' do
|
19
19
|
context "when called the first time" do
|
@@ -24,13 +24,13 @@ describe MailRoom::Arbitration::Redis do
|
|
24
24
|
it "increments the delivered flag" do
|
25
25
|
subject.deliver?(123)
|
26
26
|
|
27
|
-
expect(
|
27
|
+
expect(client.get("delivered:123")).to eq("1")
|
28
28
|
end
|
29
29
|
|
30
30
|
it "sets an expiration on the delivered flag" do
|
31
31
|
subject.deliver?(123)
|
32
32
|
|
33
|
-
expect(
|
33
|
+
expect(client.ttl("delivered:123")).to be > 0
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
@@ -46,7 +46,7 @@ describe MailRoom::Arbitration::Redis do
|
|
46
46
|
it "increments the delivered flag" do
|
47
47
|
subject.deliver?(123)
|
48
48
|
|
49
|
-
expect(
|
49
|
+
expect(client.get("delivered:123")).to eq("2")
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -60,4 +60,68 @@ describe MailRoom::Arbitration::Redis do
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
63
|
+
|
64
|
+
context 'redis client connection params' do
|
65
|
+
context 'when only url is present' do
|
66
|
+
let(:redis_url) { "redis://redis.example.com:8888" }
|
67
|
+
let(:mailbox) {
|
68
|
+
MailRoom::Mailbox.new(
|
69
|
+
arbitration_options: {
|
70
|
+
redis_url: redis_url
|
71
|
+
}
|
72
|
+
)
|
73
|
+
}
|
74
|
+
|
75
|
+
it 'client has same specified url' do
|
76
|
+
subject.deliver?(123)
|
77
|
+
|
78
|
+
expect(client.options[:url]).to eq redis_url
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'client is a instance of Redis class' do
|
82
|
+
expect(client).to be_a Redis
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'when namespace is present' do
|
87
|
+
let(:namespace) { 'mail_room' }
|
88
|
+
let(:mailbox) {
|
89
|
+
MailRoom::Mailbox.new(
|
90
|
+
arbitration_options: {
|
91
|
+
namespace: namespace
|
92
|
+
}
|
93
|
+
)
|
94
|
+
}
|
95
|
+
|
96
|
+
it 'client has same specified namespace' do
|
97
|
+
expect(client.namespace).to eq(namespace)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'client is a instance of RedisNamespace class' do
|
101
|
+
expect(client).to be_a ::Redis::Namespace
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'when sentinel is present' do
|
106
|
+
let(:redis_url) { 'redis://:mypassword@sentinel-master:6379' }
|
107
|
+
let(:sentinels) { [{ host: '10.0.0.1', port: '26379' }] }
|
108
|
+
let(:mailbox) {
|
109
|
+
MailRoom::Mailbox.new(
|
110
|
+
arbitration_options: {
|
111
|
+
redis_url: redis_url,
|
112
|
+
sentinels: sentinels
|
113
|
+
}
|
114
|
+
)
|
115
|
+
}
|
116
|
+
|
117
|
+
before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
|
118
|
+
|
119
|
+
it 'client has same specified sentinel params' do
|
120
|
+
expect(client.client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
|
121
|
+
expect(client.client.options[:host]).to eq('sentinel-master')
|
122
|
+
expect(client.client.options[:password]).to eq('mypassword')
|
123
|
+
expect(client.client.options[:sentinels]).to eq(sentinels)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
63
127
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mail_room/delivery/sidekiq'
|
3
|
+
|
4
|
+
describe MailRoom::Delivery::Sidekiq do
|
5
|
+
subject { described_class.new(options) }
|
6
|
+
let(:redis) { subject.send(:client) }
|
7
|
+
let(:options) { MailRoom::Delivery::Sidekiq::Options.new(mailbox) }
|
8
|
+
|
9
|
+
describe '#options' do
|
10
|
+
let(:redis_url) { 'redis://redis.example.com' }
|
11
|
+
|
12
|
+
context 'when only redis_url is specified' do
|
13
|
+
let(:mailbox) {
|
14
|
+
MailRoom::Mailbox.new(
|
15
|
+
delivery_method: :sidekiq,
|
16
|
+
delivery_options: {
|
17
|
+
redis_url: redis_url
|
18
|
+
}
|
19
|
+
)
|
20
|
+
}
|
21
|
+
|
22
|
+
it 'client has same specified redis_url' do
|
23
|
+
expect(redis.options[:url]).to eq(redis_url)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'client is a instance of RedisNamespace class' do
|
27
|
+
expect(redis).to be_a ::Redis
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when namespace is specified' do
|
32
|
+
let(:namespace) { 'sidekiq_mailman' }
|
33
|
+
let(:mailbox) {
|
34
|
+
MailRoom::Mailbox.new(
|
35
|
+
delivery_method: :sidekiq,
|
36
|
+
delivery_options: {
|
37
|
+
redis_url: redis_url,
|
38
|
+
namespace: namespace
|
39
|
+
}
|
40
|
+
)
|
41
|
+
}
|
42
|
+
|
43
|
+
it 'client has same specified namespace' do
|
44
|
+
expect(redis.namespace).to eq(namespace)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'client is a instance of RedisNamespace class' do
|
48
|
+
expect(redis).to be_a ::Redis::Namespace
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when sentinel is specified' do
|
53
|
+
let(:redis_url) { 'redis://:mypassword@sentinel-master:6379' }
|
54
|
+
let(:sentinels) { [{ host: '10.0.0.1', port: '26379' }] }
|
55
|
+
let(:mailbox) {
|
56
|
+
MailRoom::Mailbox.new(
|
57
|
+
delivery_method: :sidekiq,
|
58
|
+
delivery_options: {
|
59
|
+
redis_url: redis_url,
|
60
|
+
sentinels: sentinels
|
61
|
+
}
|
62
|
+
)
|
63
|
+
}
|
64
|
+
|
65
|
+
before { ::Redis::Client::Connector::Sentinel.any_instance.stubs(:resolve).returns(sentinels) }
|
66
|
+
|
67
|
+
it 'client has same specified sentinel params' do
|
68
|
+
expect(redis.client.instance_variable_get(:@connector)).to be_a Redis::Client::Connector::Sentinel
|
69
|
+
expect(redis.client.options[:host]).to eq('sentinel-master')
|
70
|
+
expect(redis.client.options[:password]).to eq('mypassword')
|
71
|
+
expect(redis.client.options[:sentinels]).to eq(sentinels)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mail_room
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Pitale
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -187,6 +187,7 @@ extensions: []
|
|
187
187
|
extra_rdoc_files: []
|
188
188
|
files:
|
189
189
|
- ".gitignore"
|
190
|
+
- ".gitlab-ci.yml"
|
190
191
|
- ".ruby-version"
|
191
192
|
- ".travis.yml"
|
192
193
|
- CHANGELOG.md
|
@@ -226,6 +227,7 @@ files:
|
|
226
227
|
- spec/lib/delivery/logger_spec.rb
|
227
228
|
- spec/lib/delivery/postback_spec.rb
|
228
229
|
- spec/lib/delivery/que_spec.rb
|
230
|
+
- spec/lib/delivery/sidekiq_spec.rb
|
229
231
|
- spec/lib/mailbox_spec.rb
|
230
232
|
- spec/lib/mailbox_watcher_spec.rb
|
231
233
|
- spec/spec_helper.rb
|
@@ -264,6 +266,7 @@ test_files:
|
|
264
266
|
- spec/lib/delivery/logger_spec.rb
|
265
267
|
- spec/lib/delivery/postback_spec.rb
|
266
268
|
- spec/lib/delivery/que_spec.rb
|
269
|
+
- spec/lib/delivery/sidekiq_spec.rb
|
267
270
|
- spec/lib/mailbox_spec.rb
|
268
271
|
- spec/lib/mailbox_watcher_spec.rb
|
269
272
|
- spec/spec_helper.rb
|