mmailer 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +253 -0
- data/Rakefile +53 -0
- data/bin/mmailer +17 -0
- data/lib/mmailer/commands.rb +46 -0
- data/lib/mmailer/config.rb +35 -0
- data/lib/mmailer/mail_helper.rb +47 -0
- data/lib/mmailer/providers.rb +42 -0
- data/lib/mmailer/server.rb +12 -0
- data/lib/mmailer/server_helper.rb +62 -0
- data/lib/mmailer/version.rb +3 -0
- data/lib/mmailer/worker.rb +49 -0
- data/lib/mmailer.rb +25 -0
- data/mmailer.gemspec +28 -0
- data/spec/spec_helper.rb +7 -0
- metadata +162 -0
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Daniel Szmulewicz
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
# Mmailer
|
2
|
+
|
3
|
+
## Rationale
|
4
|
+
|
5
|
+
The purpose of Mmailer is to allow the sending of personalized bulk email, like a newsletter, through regular SMTP providers (for example Gmail).
|
6
|
+
Regular SMTP providers imposes restrictions on how much mail you can send. Because various throttling strategies are used, and because they are not always explicit, it is sometimes difficult to evaluate whether you will succeed in sending that newsletter of yours to all of your users.
|
7
|
+
|
8
|
+
Mmailer is flexible, and well help you make sure you stay within those limits, whatever they may be. Mmailer is backend agnostic. Nor does it make any assumptions on data formats. It will process the objects you feed it. You can tell Mmailer to randomize the interval between the sending of emails, how long it should wait after a number of emails have been sent, pause the mail queue, resume it at will...
|
9
|
+
|
10
|
+
Is it any good?
|
11
|
+
---
|
12
|
+
|
13
|
+
[Yes][y].
|
14
|
+
|
15
|
+
[y]: http://news.ycombinator.com/item?id=3067434
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
$ gem install mmailer
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
All functionality is invoked via the gem's binary, mmailer.
|
24
|
+
|
25
|
+
$ mmailer
|
26
|
+
|
27
|
+
## Principle of operation
|
28
|
+
|
29
|
+
A server runs behind the scenes, managing the email queue, and you send it commands to start, pause, resume or stop.
|
30
|
+
|
31
|
+
### Server
|
32
|
+
|
33
|
+
You start the server in a terminal.
|
34
|
+
|
35
|
+
$ mmailer server
|
36
|
+
|
37
|
+
### Remote control
|
38
|
+
|
39
|
+
You issue commands in a separate terminal. To start sending emails, type:
|
40
|
+
|
41
|
+
$ mmailer start
|
42
|
+
|
43
|
+
To pause:
|
44
|
+
|
45
|
+
$ mmailer pause
|
46
|
+
|
47
|
+
To resume:
|
48
|
+
|
49
|
+
$ mmailer resume
|
50
|
+
|
51
|
+
To stop:
|
52
|
+
|
53
|
+
$ mmailer stop
|
54
|
+
|
55
|
+
To restart from the 56th element in your queue (more on this later).
|
56
|
+
|
57
|
+
$ mmailer start 56
|
58
|
+
|
59
|
+
The results of above commands are displayed in the server terminal.
|
60
|
+
|
61
|
+
### Bundler
|
62
|
+
|
63
|
+
Although this gem performs as a standalone program, nothing prevents you from adding the following in a project's Gemfile:
|
64
|
+
|
65
|
+
gem 'mmailer'
|
66
|
+
|
67
|
+
And then execute:
|
68
|
+
|
69
|
+
$ bundle
|
70
|
+
|
71
|
+
|
72
|
+
In this case, you can run
|
73
|
+
```ruby
|
74
|
+
bundle exec mmailer
|
75
|
+
```
|
76
|
+
|
77
|
+
## Configuration
|
78
|
+
|
79
|
+
`mmailer` doesn't require any external code to operate. Instead, you configure it.
|
80
|
+
You need to provide three things in order to let `mmailer` send bulk email.
|
81
|
+
|
82
|
+
* a configuration file
|
83
|
+
* template files
|
84
|
+
* environment variables
|
85
|
+
|
86
|
+
### Configuration file
|
87
|
+
|
88
|
+
Mmailer will look for a file name `config.rb` in the directory where you run it. Here is what a sample configuration file looks like:
|
89
|
+
```ruby
|
90
|
+
Mmailer.configure do |config|
|
91
|
+
config.provider = :gmail
|
92
|
+
config.from = 'Daenerys Targaryen <daenerys@house_targaryen.com>'
|
93
|
+
config.subject = "Fire and Blood"
|
94
|
+
config.time_interval = 6 #optional, default value is 6 seconds
|
95
|
+
config.mail_interval = 48 #optional, default value is 48 emails
|
96
|
+
config.sleep_time = 3600 #optional, default value is 3600 seconds
|
97
|
+
config.template = "newsletter"
|
98
|
+
config.collection = lambda do
|
99
|
+
User = Struct.new(:email, :name)
|
100
|
+
[User.new("first@email.com", "Greyjoy"), User.new("second@email.com", "Lannister"), User.new("third@email.com", "Martell")]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
* `from`: The from address that will be used in your emails.
|
106
|
+
* `subject`: The subject of your email.
|
107
|
+
* `provider`: The name of your provider. These are preset. For the moment, Gmail, Zoho and Mandrill are defined. Please add more via pull requests or by sending me mail.
|
108
|
+
* `time_interval`: The number of seconds we want to wait between emails. We use this value as a ceiling when randomizing.
|
109
|
+
* `mail_interval`: After how many emails we wait before continuing.
|
110
|
+
* `sleep_time`: How long we wait when we reach the mail interval.
|
111
|
+
* `collection`: An array of objects that respond to an `email` message. In the above example, the objects also respond to a `name` message. This will prove handy in templates. Instead of directly providing the array, it is recommended to specify a lambda that returns said array. You will then be able to make expensive calls to your database, bringing as many objects as memory permits, without impacting the server startup time.
|
112
|
+
* `template`: The path (relative to the current directory) and filename to the ERB templates for your mail, without suffix. For example, "newsletter". This means your template files are actually "newsletter.txt.erb" and "newsletter.html.erb" in the current directory.
|
113
|
+
|
114
|
+
### Templates
|
115
|
+
|
116
|
+
Templates are the body of your mail. They use the ERB templating system. Each element in your collection is available from within the template. (Much like Rails passes the instance variables from the controller to the views). Based on the collection in the previous example, a sample template would look like this:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
Dear <%= user.name %>
|
120
|
+
|
121
|
+
This is my newsletter.
|
122
|
+
|
123
|
+
Yours.
|
124
|
+
|
125
|
+
```
|
126
|
+
|
127
|
+
And the equivalent html template.
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
<p>Dear <em><%= user.name %></em>/p>
|
131
|
+
<p>This is my newsletter.</p>
|
132
|
+
<p>Yours.</p>
|
133
|
+
```
|
134
|
+
|
135
|
+
### Environment variables
|
136
|
+
|
137
|
+
Ruby can load environment variables for you. It is thus convenient to put them at the top of `config.rb`
|
138
|
+
```ruby
|
139
|
+
ENV['GMAIL_USERNAME']="username"
|
140
|
+
ENV['GMAIL_PASSWORD']="password"
|
141
|
+
ENV['MMAILER_ENV'] = "production"
|
142
|
+
```
|
143
|
+
|
144
|
+
* `MMAILER_ENV`: In production mode, emails get sent. In development mode, they get printed to STDOUT.
|
145
|
+
* `PROVIDER_USERNAME`: Username for the provider.
|
146
|
+
* `PROVIDER_PASSWORD`: Password for the provider.
|
147
|
+
|
148
|
+
You can define multiple pairs of usernames and passwords for the predefined providers.
|
149
|
+
|
150
|
+
## Real world examples
|
151
|
+
|
152
|
+
### Mongodb
|
153
|
+
|
154
|
+
This will show you how to use Mmailer when your data lives in Mongodb. We are going to use mongoid to make the queries.
|
155
|
+
|
156
|
+
Make a directory and create the configuration file and template files like previously described.
|
157
|
+
|
158
|
+
The `config.rb` would look like this:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
ENV['GMAIL_USERNAME']="username"
|
162
|
+
ENV['GMAIL_PASSWORD']="password"
|
163
|
+
ENV['MMAILER_ENV'] = "development"
|
164
|
+
|
165
|
+
require "rubygems"
|
166
|
+
require "mongoid"
|
167
|
+
require_relative "mongo_helper"
|
168
|
+
|
169
|
+
Mmailer.configure do |config|
|
170
|
+
config.provider = :gmail
|
171
|
+
config.subject = "My newsletter"
|
172
|
+
config.template = "newsletter"
|
173
|
+
config.collection = lambda { User.all.entries}
|
174
|
+
config.time_interval = 6
|
175
|
+
config.from = 'John Doe <john@example.com>'
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
Copy your mongoid.yml from your production system in the current directory. And create a mongo_helper.rb with your domain models.
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
Mongoid.load!(File.join(Dir.pwd, "mongoid.yml"), :production)
|
183
|
+
|
184
|
+
class User
|
185
|
+
include Mongoid::Document
|
186
|
+
has_many :profiles
|
187
|
+
... #the rest of your relations
|
188
|
+
end
|
189
|
+
|
190
|
+
class Profile
|
191
|
+
include Mongoid::Document
|
192
|
+
end
|
193
|
+
|
194
|
+
... #the rest of the model classes that User references
|
195
|
+
```
|
196
|
+
|
197
|
+
The content of your directory would thus look something like this:
|
198
|
+
|
199
|
+
```bash
|
200
|
+
ls -l
|
201
|
+
total 40
|
202
|
+
-rw-r--r-- 1 daniel 1000 424 יול 14 03:43 config.rb
|
203
|
+
-rw-r--r-- 1 daniel 1000 3587 יול 10 04:08 mongo_helper.rb
|
204
|
+
-rw-r--r-- 1 daniel 1000 3027 יול 10 03:39 mongoid.yml
|
205
|
+
-rw-r--r-- 1 daniel 1000 81 יול 14 03:44 newsletter.html.erb
|
206
|
+
-rw-r--r-- 1 daniel 1000 60 יול 14 03:44 newsletter.txt.erb
|
207
|
+
```
|
208
|
+
|
209
|
+
You are now ready to send your newsletter. In one terminal, type `mmailer server`, in another type `mmailer start`. Output will be displayed in the server terminal.
|
210
|
+
|
211
|
+
### More examples
|
212
|
+
|
213
|
+
More configuration examples soon. (Please don't hesitate to contribute your configurations.)
|
214
|
+
|
215
|
+
## Architecture & Implementation
|
216
|
+
|
217
|
+
### DRb
|
218
|
+
|
219
|
+
The server exposes an object representing the state of your queue (started/stopped/paused). When the client asks the server to start sending email, the server spawns a thread which will subsequently check on that state object after each email sending, thus knowing if it should proceed, halt, or change behavior in other ways. DRb is used to implement this model.
|
220
|
+
|
221
|
+
### State machine
|
222
|
+
|
223
|
+
We use MicroMachine, a minimal finite state machine, to help with the state transitioning.
|
224
|
+
|
225
|
+
### CLI
|
226
|
+
|
227
|
+
We used Thor to provide a command line interface.
|
228
|
+
|
229
|
+
### Web interface
|
230
|
+
|
231
|
+
This program will be best served with some sort of GUI. A web-based interface (using Sinatra) is under consideration.
|
232
|
+
|
233
|
+
## Status
|
234
|
+
|
235
|
+
This program makes me happy as it solves one of my problems. It may not be a beginner's friendly program, however. You might want to wait for the web interface if it all seems a little bit too involved.
|
236
|
+
|
237
|
+
## TODO
|
238
|
+
|
239
|
+
* [] Web interface
|
240
|
+
* [X] Command-line interface
|
241
|
+
* [] Documentation
|
242
|
+
|
243
|
+
## Spam
|
244
|
+
|
245
|
+
Mmailer is a mail sending tool. Don't use it for spamming purposes. Spam is evil.
|
246
|
+
|
247
|
+
## Contributing
|
248
|
+
|
249
|
+
1. Fork it
|
250
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
251
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
252
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
253
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'mmailer'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task default: :spec
|
8
|
+
|
9
|
+
desc "Open an irb session preloaded with this library"
|
10
|
+
task :console do
|
11
|
+
sh "irb -rubygems -I lib -I lib/mmailer -r mmailer.rb"
|
12
|
+
end
|
13
|
+
|
14
|
+
task :master do
|
15
|
+
Mmailer.start_server
|
16
|
+
end
|
17
|
+
|
18
|
+
task :start do
|
19
|
+
client(:start)
|
20
|
+
end
|
21
|
+
|
22
|
+
task :pause do
|
23
|
+
client(:pause)
|
24
|
+
end
|
25
|
+
task :resume do
|
26
|
+
client(:resume)
|
27
|
+
end
|
28
|
+
task :stop do
|
29
|
+
client(:stop)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def client(cmd)
|
35
|
+
require 'drb/drb'
|
36
|
+
uri = 'druby://localhost:12345'
|
37
|
+
begin
|
38
|
+
obj = DRbObject.new_with_uri(uri)
|
39
|
+
obj.send(cmd)
|
40
|
+
rescue DRb::DRbConnError => e
|
41
|
+
puts e.message + "\nIs the server running? (You can start the server with)"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
=begin
|
47
|
+
#uri = ARGV.shift
|
48
|
+
if obj.up?
|
49
|
+
puts "OK, connection with server established"
|
50
|
+
else
|
51
|
+
puts "There was a problem establishing a connection with the server"
|
52
|
+
end
|
53
|
+
=end
|
data/bin/mmailer
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
### Preliminaries to make the `require`'s below work
|
4
|
+
|
5
|
+
# resolve bin path, ignoring symlinks
|
6
|
+
require "pathname"
|
7
|
+
bin_file = Pathname.new(__FILE__).realpath
|
8
|
+
|
9
|
+
# add self to libpath
|
10
|
+
$:.unshift File.expand_path("../../lib", bin_file)
|
11
|
+
|
12
|
+
###
|
13
|
+
|
14
|
+
require 'thor'
|
15
|
+
require 'mmailer/commands'
|
16
|
+
|
17
|
+
MyCLI.start(ARGV)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class MyCLI < Thor
|
2
|
+
desc "server", "Start server."
|
3
|
+
|
4
|
+
def server
|
5
|
+
require 'mmailer'
|
6
|
+
Mmailer.start_server
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "start FROM", "Start an email run from FROM (default is 0, start of the collection)."
|
10
|
+
def start(from=0)
|
11
|
+
client(:start, from.to_i)
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "pause", "Pause an email run."
|
15
|
+
def pause
|
16
|
+
client(:pause)
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "resume", "Resume an email run."
|
20
|
+
def resume
|
21
|
+
client(:resume)
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "stop", "Stop an email run and exit server."
|
25
|
+
def stop
|
26
|
+
client(:stop)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def client(cmd, args=nil)
|
33
|
+
require 'drb/drb'
|
34
|
+
uri = 'druby://localhost:12345'
|
35
|
+
begin
|
36
|
+
obj = DRbObject.new_with_uri(uri)
|
37
|
+
if args
|
38
|
+
obj.send(cmd, args)
|
39
|
+
else
|
40
|
+
obj.send(cmd)
|
41
|
+
end
|
42
|
+
rescue DRb::DRbConnError => e
|
43
|
+
puts e.message + "\nIs the server running? (You can start the server with `mmailer server`)"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Mmailer
|
2
|
+
class << self
|
3
|
+
attr_accessor :configuration
|
4
|
+
|
5
|
+
def configuration
|
6
|
+
@configuration ||= Configuration.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def configure
|
10
|
+
yield(configuration) if block_given?
|
11
|
+
end
|
12
|
+
|
13
|
+
def provider
|
14
|
+
self.configuration = provider
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class Configuration
|
20
|
+
attr_accessor :provider, :from, :template, :subject, :server_port, :collection, :time_interval, :mail_interval, :sleep_time
|
21
|
+
|
22
|
+
def sleep_time
|
23
|
+
@sleep_time || 3600
|
24
|
+
end
|
25
|
+
|
26
|
+
def time_interval
|
27
|
+
@time_interval || 6
|
28
|
+
end
|
29
|
+
|
30
|
+
def mail_interval
|
31
|
+
@mail_interval || 48
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Mmailer
|
2
|
+
class MailHelper
|
3
|
+
attr_reader :template, :subject, :from
|
4
|
+
|
5
|
+
def initialize(args)
|
6
|
+
set_provider args.fetch(:provider, :mandrill)
|
7
|
+
@template=args[:template]
|
8
|
+
@subject=args[:subject]
|
9
|
+
@from=args[:from]
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_provider(provider)
|
13
|
+
providers = {gmail: Providers.gmail, mandrill: Providers.mandrill, zoho: Providers.zoho}
|
14
|
+
Mail.defaults(&providers[provider])
|
15
|
+
end
|
16
|
+
|
17
|
+
def send_email(user)
|
18
|
+
|
19
|
+
mail = Mail.new do
|
20
|
+
to user.email
|
21
|
+
end
|
22
|
+
mail.from = from
|
23
|
+
mail.subject = subject
|
24
|
+
|
25
|
+
text_part = Mail::Part.new
|
26
|
+
text_part.body=ERB.new(File.read(Dir.pwd + "/" + Mmailer.configuration.template + ".txt.erb")).result(binding)
|
27
|
+
|
28
|
+
html_part = Mail::Part.new
|
29
|
+
html_part.content_type='text/html; charset=UTF-8'
|
30
|
+
html_part.body=ERB.new(File.read(Dir.pwd + "/" + Mmailer.configuration.template + ".html.erb")).result(binding)
|
31
|
+
|
32
|
+
mail.text_part = text_part
|
33
|
+
mail.html_part = html_part
|
34
|
+
#when Non US-ASCII detected and no charset defined. Defaulting to UTF-8, set your own if this is incorrect.
|
35
|
+
mail.charset = 'UTF-8'
|
36
|
+
|
37
|
+
case ENV['MMAILER_ENV']
|
38
|
+
when "production"
|
39
|
+
mail.deliver!
|
40
|
+
when "development"
|
41
|
+
puts mail.to_s
|
42
|
+
else
|
43
|
+
mail.deliver!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Mmailer
|
2
|
+
module Providers
|
3
|
+
|
4
|
+
class << self
|
5
|
+
attr_accessor :mandrill, :gmail, :zoho
|
6
|
+
end
|
7
|
+
|
8
|
+
@mandrill = Proc.new do
|
9
|
+
delivery_method :smtp, {
|
10
|
+
:port => 587,
|
11
|
+
:address => 'smtp.mandrillapp.com',
|
12
|
+
:user_name => ENV['MANDRILL_USERNAME'],
|
13
|
+
:password => ENV['MANDRILL_PASSWORD'],
|
14
|
+
:domain => 'heroku.com',
|
15
|
+
:authentication => :plain
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
@gmail = Proc.new do
|
20
|
+
delivery_method :smtp, {
|
21
|
+
:port => 587,
|
22
|
+
:address => "smtp.gmail.com",
|
23
|
+
:user_name => ENV['GMAIL_USERNAME'],
|
24
|
+
:password => ENV['GMAIL_PASSWORD'],
|
25
|
+
:authentication => :plain,
|
26
|
+
:enable_starttls_auto => true
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
@zoho = Proc.new do
|
31
|
+
delivery_method :smtp, {
|
32
|
+
:port => 587,
|
33
|
+
:address => "smtp.zoho.com",
|
34
|
+
:user_name => ENV['ZOHO_USERNAME'],
|
35
|
+
:password => ENV['ZOHO_PASSWORD'],
|
36
|
+
:authentication => :plain,
|
37
|
+
:enable_starttls_auto => true
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Mmailer
|
2
|
+
class ServerHelper
|
3
|
+
attr_accessor :stream, :worker, :machine
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@stream = $stdout
|
7
|
+
self.machine = MicroMachine.new(:stopped)
|
8
|
+
machine.when(:start, :stopped => :started)
|
9
|
+
machine.when(:resume, :paused => :started)
|
10
|
+
machine.when(:stop, :started => :stopped, :paused => :stopped)
|
11
|
+
machine.when(:pause, :started => :paused)
|
12
|
+
end
|
13
|
+
|
14
|
+
def display_state
|
15
|
+
stream.puts state
|
16
|
+
end
|
17
|
+
|
18
|
+
def state
|
19
|
+
machine.state
|
20
|
+
end
|
21
|
+
|
22
|
+
def puts(str)
|
23
|
+
stream.puts(str)
|
24
|
+
end
|
25
|
+
|
26
|
+
def up?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def resume
|
31
|
+
if machine.trigger(:resume)
|
32
|
+
display_state
|
33
|
+
machine.state
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def stop
|
38
|
+
if machine.trigger(:stop)
|
39
|
+
display_state
|
40
|
+
machine.state
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def pause
|
45
|
+
if machine.trigger(:pause)
|
46
|
+
display_state
|
47
|
+
machine.state
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def start(from=0)
|
52
|
+
if machine.trigger(:start)
|
53
|
+
puts "starting from #{from}"
|
54
|
+
Thread.abort_on_exception = true
|
55
|
+
@worker = Thread.new(from) do |from|
|
56
|
+
Worker.new(from)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Mmailer
|
2
|
+
class Worker
|
3
|
+
# two froms, not equal
|
4
|
+
attr_reader :obj, :mailHelper, :collection, :time_interval, :mail_interval, :sleep_time, :from
|
5
|
+
|
6
|
+
def initialize(from)
|
7
|
+
@from = from
|
8
|
+
@obj = DRbObject.new_with_uri('druby://localhost:12345')
|
9
|
+
meta = { subject: Mmailer.configuration.subject, from: Mmailer.configuration.from, template: Mmailer.configuration.template, provider: Mmailer.configuration.provider }
|
10
|
+
@mailHelper = MailHelper.new(meta)
|
11
|
+
@time_interval = Mmailer.configuration.time_interval
|
12
|
+
@mail_interval = Mmailer.configuration.mail_interval
|
13
|
+
@sleep_time = Mmailer.configuration.sleep_time
|
14
|
+
load_collection
|
15
|
+
exec
|
16
|
+
end
|
17
|
+
|
18
|
+
def exec
|
19
|
+
while not collection.empty? do
|
20
|
+
case obj.state
|
21
|
+
when :paused
|
22
|
+
sleep 1
|
23
|
+
when :started
|
24
|
+
index ||= from; index += 1
|
25
|
+
user = collection.shift
|
26
|
+
obj.puts "#{index}: #{user.email}"
|
27
|
+
mailHelper.send_email(user) if not user.email.nil?
|
28
|
+
sleep rand(time_interval)
|
29
|
+
if index % mail_interval == 0
|
30
|
+
obj.puts "#{mail_interval} element, going to sleep for #{sleep_time} seconds"
|
31
|
+
sleep sleep_time
|
32
|
+
end
|
33
|
+
when :stopped
|
34
|
+
break
|
35
|
+
end
|
36
|
+
end
|
37
|
+
obj.puts "Exiting worker, stopping server"
|
38
|
+
DRb::stop_service
|
39
|
+
Thread.exit
|
40
|
+
end
|
41
|
+
|
42
|
+
def load_collection
|
43
|
+
@collection = Mmailer.configuration.collection.lambda? ? Mmailer.configuration.collection.call : Mmailer.configuration.collection
|
44
|
+
collection.shift(from)
|
45
|
+
obj.puts "Loaded #{collection.count} entries"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
data/lib/mmailer.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "mmailer/version"
|
2
|
+
|
3
|
+
module Mmailer
|
4
|
+
|
5
|
+
require 'mail'
|
6
|
+
require 'micromachine'
|
7
|
+
require 'erb'
|
8
|
+
require 'drb/drb'
|
9
|
+
require 'mmailer/config'
|
10
|
+
require 'mmailer/providers'
|
11
|
+
require 'mmailer/server'
|
12
|
+
require 'mmailer/server_helper'
|
13
|
+
require 'mmailer/mail_helper'
|
14
|
+
require 'mmailer/worker'
|
15
|
+
|
16
|
+
## read config
|
17
|
+
config = File.join(Dir.pwd, 'config.rb')
|
18
|
+
|
19
|
+
if File.exists? config
|
20
|
+
load config
|
21
|
+
else
|
22
|
+
puts "No configuration file found"
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/mmailer.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mmailer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mmailer"
|
8
|
+
spec.version = Mmailer::VERSION
|
9
|
+
spec.authors = ["Daniel Szmulewicz"]
|
10
|
+
spec.email = ["danielsz@freeshell.org"]
|
11
|
+
spec.description = %q{Bulk mailer with remote control (drb server)}
|
12
|
+
spec.summary = %q{Bulk mailing the Ruby way}
|
13
|
+
spec.homepage = "https://github.com/danielsz/mmailer"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
|
25
|
+
spec.add_runtime_dependency 'thor'
|
26
|
+
spec.add_runtime_dependency 'mail'
|
27
|
+
spec.add_runtime_dependency 'micromachine'
|
28
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mmailer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Daniel Szmulewicz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: thor
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: mail
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: micromachine
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description: Bulk mailer with remote control (drb server)
|
111
|
+
email:
|
112
|
+
- danielsz@freeshell.org
|
113
|
+
executables:
|
114
|
+
- mmailer
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- .gitignore
|
119
|
+
- CHANGELOG.md
|
120
|
+
- Gemfile
|
121
|
+
- LICENSE.txt
|
122
|
+
- README.md
|
123
|
+
- Rakefile
|
124
|
+
- bin/mmailer
|
125
|
+
- lib/mmailer.rb
|
126
|
+
- lib/mmailer/commands.rb
|
127
|
+
- lib/mmailer/config.rb
|
128
|
+
- lib/mmailer/mail_helper.rb
|
129
|
+
- lib/mmailer/providers.rb
|
130
|
+
- lib/mmailer/server.rb
|
131
|
+
- lib/mmailer/server_helper.rb
|
132
|
+
- lib/mmailer/version.rb
|
133
|
+
- lib/mmailer/worker.rb
|
134
|
+
- mmailer.gemspec
|
135
|
+
- spec/spec_helper.rb
|
136
|
+
homepage: https://github.com/danielsz/mmailer
|
137
|
+
licenses:
|
138
|
+
- MIT
|
139
|
+
post_install_message:
|
140
|
+
rdoc_options: []
|
141
|
+
require_paths:
|
142
|
+
- lib
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
144
|
+
none: false
|
145
|
+
requirements:
|
146
|
+
- - ! '>='
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
none: false
|
151
|
+
requirements:
|
152
|
+
- - ! '>='
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project:
|
157
|
+
rubygems_version: 1.8.23
|
158
|
+
signing_key:
|
159
|
+
specification_version: 3
|
160
|
+
summary: Bulk mailing the Ruby way
|
161
|
+
test_files:
|
162
|
+
- spec/spec_helper.rb
|