mongo_mailer 0.0.0 → 0.1.3
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.
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
|