mongo_mailer 0.0.0 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of mongo_mailer might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.gitignore +10 -13
- data/.travis.yml +6 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +112 -61
- data/README.md +112 -0
- data/Rakefile +7 -4
- data/bin/console +7 -0
- data/bin/mongo_mailer +11 -0
- data/bin/worker +6 -0
- data/config/boot.rb +12 -0
- data/config/mongo_mailer.yml.example +38 -0
- data/lib/generators/mongo_mailer/mongo_mailer_generator.rb +10 -0
- data/lib/generators/mongo_mailer/templates/mongo_mailer.yml +38 -0
- data/lib/mongo_mailer.rb +9 -2
- data/lib/mongo_mailer/capistrano.rb +33 -0
- data/lib/mongo_mailer/capistrano3.rb +49 -0
- data/lib/mongo_mailer/common/deliveries_counter.rb +18 -0
- data/lib/mongo_mailer/common/has_mongo_collection.rb +36 -0
- data/lib/mongo_mailer/common/mail_queue.rb +49 -0
- data/lib/mongo_mailer/configuration.rb +127 -0
- data/lib/mongo_mailer/core_ext/hash.rb +12 -0
- data/lib/mongo_mailer/delivery_methods/mongo_queue.rb +28 -0
- data/lib/mongo_mailer/delivery_methods/test.rb +38 -0
- data/lib/mongo_mailer/rails.rb +5 -0
- data/lib/mongo_mailer/standalone.rb +6 -0
- data/lib/mongo_mailer/standalone/final_deliverer.rb +35 -0
- data/lib/mongo_mailer/standalone/worker.rb +23 -0
- data/lib/mongo_mailer/version.rb +3 -0
- data/log/.gitkeep +0 -0
- data/mongo_mailer.gemspec +25 -26
- data/spec/lib/common/deliveries_counter_spec.rb +22 -0
- data/spec/lib/common/mail_queue_spec.rb +68 -0
- data/spec/lib/configuration_spec.rb +26 -0
- data/spec/lib/standalone/final_deliverer_spec.rb +40 -0
- data/spec/rails/delivery_methods/mongo_queue_spec.rb +33 -0
- data/spec/spec_helper.rb +29 -0
- metadata +97 -44
- data/LICENSE +0 -21
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
class MongoMailerGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
self.source_paths << File.join(File.dirname(__FILE__), 'templates')
|
6
|
+
|
7
|
+
def create_config_files
|
8
|
+
template 'mongo_mailer.yml', 'config/mongo_mailer.yml.example'
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
development: &development
|
2
|
+
mongodb:
|
3
|
+
host: localhost
|
4
|
+
port: 27017
|
5
|
+
name: mongo_mailer
|
6
|
+
base_delivery_method: smtp
|
7
|
+
base_delivery_settings:
|
8
|
+
user_name: "username"
|
9
|
+
password: "apikey"
|
10
|
+
domain: "example.com"
|
11
|
+
address: "smtp1.example.com"
|
12
|
+
port: 587
|
13
|
+
authentication: plain
|
14
|
+
enable_starttls_auto: true
|
15
|
+
emergency_delivery_method: smtp
|
16
|
+
emergency_delivery_settings:
|
17
|
+
user_name: "username"
|
18
|
+
password: "apikey"
|
19
|
+
domain: "example.com"
|
20
|
+
address: "smtp2.example.com"
|
21
|
+
port: 587
|
22
|
+
authentication: plain
|
23
|
+
enable_starttls_auto: true
|
24
|
+
log_level: info
|
25
|
+
daemon_options:
|
26
|
+
dir_mode: !ruby/symbol normal
|
27
|
+
log_dir: log
|
28
|
+
dir: log
|
29
|
+
log_output: true
|
30
|
+
backtrace: true
|
31
|
+
multiple: false
|
32
|
+
production:
|
33
|
+
<<: *development
|
34
|
+
test:
|
35
|
+
<<: *development
|
36
|
+
base_delivery_method: test1
|
37
|
+
emergency_delivery_method: test2
|
38
|
+
log_level: error
|
data/lib/mongo_mailer.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
+
require 'active_support/all'
|
1
2
|
require 'mail'
|
2
3
|
require 'mongo'
|
4
|
+
require 'daemons'
|
5
|
+
require 'singleton'
|
6
|
+
require 'mongo_mailer/version'
|
7
|
+
require 'mongo_mailer/configuration'
|
3
8
|
|
4
|
-
|
5
|
-
|
9
|
+
require 'mongo_mailer/common/has_mongo_collection'
|
10
|
+
Dir[File.expand_path('../mongo_mailer/common/*', __FILE__)].each do |lib|
|
11
|
+
require lib
|
12
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
Capistrano::Configuration.instance.load do
|
2
|
+
after "deploy", "mongo_mailer:consider_restarting"
|
3
|
+
|
4
|
+
namespace :mongo_mailer do
|
5
|
+
def roles
|
6
|
+
fetch(:mongo_mailer_server_role, :app)
|
7
|
+
end
|
8
|
+
|
9
|
+
def command_prefix
|
10
|
+
"cd #{current_path} && bundle exec mongo_mailer"
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Stop the mongo_mailer process"
|
14
|
+
task :stop, :roles => lambda { roles } do
|
15
|
+
run "#{command_prefix} stop #{rails_env}"
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Start the mongo_mailer process"
|
19
|
+
task :start, :roles => lambda { roles } do
|
20
|
+
run "#{command_prefix} start #{rails_env}"
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Restart the mongo_mailer process"
|
24
|
+
task :restart, :roles => lambda { roles } do
|
25
|
+
run "#{command_prefix} restart #{rails_env}"
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "Restart the mongo_mailer process"
|
29
|
+
task :consider_restarting, :roles => lambda { roles } do
|
30
|
+
logger.info "Consider restarting mongo_mailer after deploy: `bundle exec cap #{fetch(:stage)} mongo_mailer:restart`"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
after "deploy", "mongo_mailer:consider_restarting"
|
2
|
+
|
3
|
+
namespace :load do
|
4
|
+
task :defaults do
|
5
|
+
set :mongo_mailer_server_role, -> { :app }
|
6
|
+
# Rbenv, Chruby, and RVM integration
|
7
|
+
set :rbenv_map_bins, fetch(:rbenv_map_bins).to_a.concat(%w(mongo_mailer))
|
8
|
+
set :rvm_map_bins, fetch(:rvm_map_bins).to_a.concat(%w(mongo_mailer))
|
9
|
+
set :chruby_map_bins, fetch(:chruby_map_bins).to_a.concat(%w(mongo_mailer))
|
10
|
+
# Bundler integration
|
11
|
+
set :bundle_bins, fetch(:bundle_bins).to_a.concat(%w(mongo_mailer))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
namespace :mongo_mailer do
|
16
|
+
desc "Stop the mongo_mailer process"
|
17
|
+
task :stop do
|
18
|
+
on roles fetch(:mongo_mailer_server_role) do
|
19
|
+
within(current_path) do
|
20
|
+
execute :mongo_mailer, :stop, fetch(:rails_env)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Start the mongo_mailer process"
|
26
|
+
task :start do
|
27
|
+
on roles fetch(:mongo_mailer_server_role) do
|
28
|
+
within(current_path) do
|
29
|
+
execute :mongo_mailer, :start, fetch(:rails_env)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Restart the mongo_mailer process"
|
35
|
+
task :restart do
|
36
|
+
on roles fetch(:mongo_mailer_server_role) do
|
37
|
+
within(current_path) do
|
38
|
+
execute :mongo_mailer, :restart, fetch(:rails_env)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Consider restarting the mongo_mailer process"
|
44
|
+
task :consider_restarting do
|
45
|
+
on roles fetch(:mongo_mailer_server_role) do
|
46
|
+
info "Consider restarting mongo_mailer after deploy: `bundle exec cap #{fetch(:stage)} mongo_mailer:restart`"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module MongoMailer
|
2
|
+
class DeliveriesCounter
|
3
|
+
include Singleton
|
4
|
+
include ::MongoMailer::HasMongoCollection
|
5
|
+
|
6
|
+
def increment(type)
|
7
|
+
collection.update({ type: type }, { '$inc' => { value: 1 } }, {upsert: true})
|
8
|
+
end
|
9
|
+
|
10
|
+
def by_type(type)
|
11
|
+
collection.find_one({type: type})
|
12
|
+
end
|
13
|
+
|
14
|
+
def all
|
15
|
+
collection.find.to_a
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module MongoMailer
|
2
|
+
module HasMongoCollection
|
3
|
+
def self.included(klass)
|
4
|
+
klass.send :extend, ClassMethods
|
5
|
+
klass.send :include, InstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module InstanceMethods
|
9
|
+
def collection
|
10
|
+
self.class.collection
|
11
|
+
end
|
12
|
+
|
13
|
+
def configuration
|
14
|
+
self.class.configuration
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def collection_name=(name)
|
20
|
+
@collection_name = name
|
21
|
+
end
|
22
|
+
|
23
|
+
def collection_name
|
24
|
+
@collection_name ||= self.name.demodulize.underscore.pluralize
|
25
|
+
end
|
26
|
+
|
27
|
+
def collection
|
28
|
+
@collection ||= Configuration.instance.mongodb.collection(collection_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def configuration
|
32
|
+
Configuration.instance
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module MongoMailer
|
2
|
+
class MailQueue
|
3
|
+
include Singleton
|
4
|
+
include MongoMailer::HasMongoCollection
|
5
|
+
|
6
|
+
MAX_RETIRES_COUNT = 5
|
7
|
+
|
8
|
+
def find_and_deliver!
|
9
|
+
item = get_oldest
|
10
|
+
|
11
|
+
return unless item
|
12
|
+
|
13
|
+
deliver!(item)
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_oldest
|
17
|
+
collection.find_and_modify(
|
18
|
+
query: { invalid: nil },
|
19
|
+
remove: true,
|
20
|
+
sort: { :'$natural' => 1 })
|
21
|
+
end
|
22
|
+
|
23
|
+
def invalid_items
|
24
|
+
collection.all(
|
25
|
+
{ invalid: true },
|
26
|
+
sort: { :'$natural' => 1 })
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear_invalid_items!
|
30
|
+
collection.remove({ invalid: true })
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def deliver!(item)
|
36
|
+
begin
|
37
|
+
::MongoMailer::FinalDeliverer.new(item['encoded']).deliver!
|
38
|
+
collection.remove(item)
|
39
|
+
return true
|
40
|
+
rescue => e
|
41
|
+
configuration.log_error(e)
|
42
|
+
item['retries_count'] = (item['retries_count'] || 0) + 1
|
43
|
+
item['invalid'] = true if item['retries_count'] > MAX_RETIRES_COUNT
|
44
|
+
collection.insert(item)
|
45
|
+
return false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'mongo_mailer/core_ext/hash'
|
3
|
+
|
4
|
+
module MongoMailer
|
5
|
+
class Configuration
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
class MissingConfigurationFile < StandardError; end
|
9
|
+
class MissingConfigurationData < StandardError; end
|
10
|
+
class MissingConfigurationVariables < StandardError; end
|
11
|
+
|
12
|
+
REQUIRED_KEYS = [:mongodb, :base_delivery_method, :emergency_delivery_method]
|
13
|
+
|
14
|
+
def env
|
15
|
+
@env ||= defined?(Rails) ? Rails.env.to_s : ENV['RAILS_ENV']
|
16
|
+
end
|
17
|
+
|
18
|
+
def root
|
19
|
+
return @root if @root
|
20
|
+
@root = Rails.root if defined?(Rails)
|
21
|
+
end
|
22
|
+
|
23
|
+
def root=(path)
|
24
|
+
@root = Pathname.new(path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def configuration
|
28
|
+
@configuration ||= load!
|
29
|
+
end
|
30
|
+
|
31
|
+
def load!
|
32
|
+
unless root
|
33
|
+
warn "`MongoMailer::Configuration.instance.root` does not seem to be set!"
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
|
37
|
+
@configuration = begin
|
38
|
+
load_yml('config/mongo_mailer.yml')
|
39
|
+
rescue MissingConfigurationFile
|
40
|
+
warn "`config/mongo_mailer.yml` seems to be missing, loading `config/mongo_mailer.yml.example` file instead."
|
41
|
+
load_yml('config/mongo_mailer.yml.example')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def verify!
|
46
|
+
missing_keys = REQUIRED_KEYS - configuration.keys
|
47
|
+
unless missing_keys.empty?
|
48
|
+
raise MissingConfigurationVariables("Following variables are missing in your configuration file: #{missing_keys.join(',')}")
|
49
|
+
end
|
50
|
+
return true
|
51
|
+
end
|
52
|
+
|
53
|
+
def mongodb
|
54
|
+
configuration[:mongodb][:name] ||= 'mongo_mailer'
|
55
|
+
@mongodb ||= ::Mongo::MongoClient.new(configuration[:mongodb][:host], configuration[:mongodb][:port]).db("#{configuration[:mongodb][:name]}_#{env}")
|
56
|
+
end
|
57
|
+
|
58
|
+
def lookup_delivery_method(method)
|
59
|
+
method = ::Mail::Configuration.instance.lookup_delivery_method(method.to_s)
|
60
|
+
return method unless method.is_a?(String)
|
61
|
+
|
62
|
+
case method.to_sym
|
63
|
+
when :test1 then MongoMailer::DeliveryMethods::Test1Mailer
|
64
|
+
when :test2 then MongoMailer::DeliveryMethods::Test2Mailer
|
65
|
+
else
|
66
|
+
raise "Unknown delivery method: #{method}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def base_delivery
|
71
|
+
@base_delivery ||= [
|
72
|
+
lookup_delivery_method(configuration[:base_delivery_method]),
|
73
|
+
configuration[:base_delivery_settings]
|
74
|
+
]
|
75
|
+
end
|
76
|
+
|
77
|
+
def emergency_delivery
|
78
|
+
@emergency_delivery ||= [
|
79
|
+
lookup_delivery_method(configuration[:emergency_delivery_method]),
|
80
|
+
configuration[:emergency_delivery_settings]
|
81
|
+
]
|
82
|
+
end
|
83
|
+
|
84
|
+
def full_daemon_options
|
85
|
+
opts = configuration[:daemon_options]
|
86
|
+
opts[:log_dir] = root.join(opts[:log_dir]).to_s if opts[:log_dir]
|
87
|
+
opts[:dir] = root.join(opts[:dir]).to_s if opts[:dir]
|
88
|
+
return opts
|
89
|
+
end
|
90
|
+
|
91
|
+
def logger
|
92
|
+
@logger ||= init_logger
|
93
|
+
end
|
94
|
+
|
95
|
+
def log_error(e)
|
96
|
+
msg = [e.inspect, e.backtrace].flatten.join("\n")
|
97
|
+
logger.error("[ERROR] #{msg}")
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def init_logger
|
103
|
+
logger = Logger.new(File.join(self.root, 'log/mongo_queue.log'), 'weekly')
|
104
|
+
logger.formatter = Logger::Formatter.new
|
105
|
+
|
106
|
+
log_level = (configuration[:log_level] || :info)
|
107
|
+
logger.level = Logger.const_get(log_level.to_s.upcase)
|
108
|
+
logger
|
109
|
+
end
|
110
|
+
|
111
|
+
def load_yml(file)
|
112
|
+
path = File.join(self.root, file)
|
113
|
+
|
114
|
+
unless File.exists?(path)
|
115
|
+
raise MissingConfigurationFile.new("File #{path} was found")
|
116
|
+
end
|
117
|
+
|
118
|
+
yml = ::YAML.load_file(path)
|
119
|
+
|
120
|
+
if yml[self.env].is_a?(Hash)
|
121
|
+
return yml[self.env].deep_symbolize_keys
|
122
|
+
else
|
123
|
+
raise MissingConfigurationData.new("Configuration data for #{self.env} was not found in #{path}")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Hash
|
2
|
+
# Hash#deep_symbolize_keys
|
3
|
+
# based on
|
4
|
+
# https://github.com/svenfuchs/i18n/blob/master/lib/i18n/core_ext/hash.rb
|
5
|
+
def deep_symbolize_keys
|
6
|
+
inject({}) { |result, (key, value)|
|
7
|
+
value = value.deep_symbolize_keys if value.is_a?(self.class)
|
8
|
+
result[(key.to_sym rescue key) || key] = value
|
9
|
+
result
|
10
|
+
}
|
11
|
+
end unless self.method_defined?(:deep_symbolize_keys)
|
12
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'mail/check_delivery_params'
|
2
|
+
|
3
|
+
module MongoMailer
|
4
|
+
module DeliveryMethods
|
5
|
+
class MongoQueue
|
6
|
+
attr_accessor :settings
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@settings = options.merge(return_response: true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def deliver!(mail)
|
13
|
+
Mail::CheckDeliveryParams.check(mail)
|
14
|
+
collection.insert({encoded: mail.encoded, uuid: get_uuid})
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def get_uuid
|
20
|
+
::SecureRandom.hex(8)
|
21
|
+
end
|
22
|
+
|
23
|
+
def collection
|
24
|
+
@collection ||= ::MongoMailer::MailQueue.instance.collection
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'mail/check_delivery_params'
|
2
|
+
|
3
|
+
module MongoMailer
|
4
|
+
module DeliveryMethods
|
5
|
+
module CommonTestMailer
|
6
|
+
def self.included(klass)
|
7
|
+
klass.send :extend, ClassMethods
|
8
|
+
klass.send :include, InstanceMethods
|
9
|
+
klass.send :attr_accessor, :settings
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
def initialize(*args)
|
14
|
+
@settings = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def deliver!(mail)
|
18
|
+
Mail::CheckDeliveryParams.check(mail)
|
19
|
+
self.class.deliveries << mail
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def deliveries
|
25
|
+
@deliveries ||= []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Test1Mailer
|
31
|
+
include CommonTestMailer
|
32
|
+
end
|
33
|
+
|
34
|
+
class Test2Mailer
|
35
|
+
include CommonTestMailer
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|