leftbrained-ar_mailer 2.1.9
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.
- data/History.txt +163 -0
- data/LICENSE.txt +28 -0
- data/README.rdoc +144 -0
- data/Rakefile +76 -0
- data/bin/ar_sendmail +6 -0
- data/generators/ar_mailer/ar_mailer_generator.rb +25 -0
- data/generators/ar_mailer/templates/migration.rb +16 -0
- data/generators/ar_mailer/templates/model.rb +2 -0
- data/lib/action_mailer/ar_mailer.rb +34 -0
- data/lib/action_mailer/ar_sendmail.rb +499 -0
- data/lib/leftbrained-ar_mailer.rb +1 -0
- data/lib/smtp_tls.rb +105 -0
- data/share/bsd/ar_sendmail +30 -0
- data/share/linux/ar_sendmail +78 -0
- data/share/linux/ar_sendmail.conf +30 -0
- data/test/resources/action_mailer.rb +197 -0
- data/test/test_armailer.rb +58 -0
- data/test/test_arsendmail.rb +576 -0
- data/test/test_helper.rb +9 -0
- metadata +101 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class <%= migration_name %> < ActiveRecord::Migration
|
|
2
|
+
def self.up
|
|
3
|
+
create_table :<%= table_name %> do |t|
|
|
4
|
+
t.column :from, :string
|
|
5
|
+
t.column :to, :string
|
|
6
|
+
t.column :last_send_attempt, :integer, :default => 0
|
|
7
|
+
t.column :mail, :text
|
|
8
|
+
t.column :worker, :string, :default=>""
|
|
9
|
+
t.column :created_on, :datetime
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.down
|
|
14
|
+
drop_table :<%= table_name %>
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Adds sending email through an ActiveRecord table as a delivery method for
|
|
3
|
+
# ActionMailer.
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
class ActionMailer::Base
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Set the email class for deliveries. Handle class reloading issues which prevents caching the email class.
|
|
10
|
+
#
|
|
11
|
+
@@email_class_name = 'Email'
|
|
12
|
+
|
|
13
|
+
def self.email_class=(klass)
|
|
14
|
+
@@email_class_name = klass.to_s
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.email_class
|
|
18
|
+
@@email_class_name.constantize
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Adds +mail+ to the Email table. Only the first From address for +mail+ is
|
|
23
|
+
# used.
|
|
24
|
+
|
|
25
|
+
def perform_delivery_activerecord(mail)
|
|
26
|
+
destinations = mail.destinations
|
|
27
|
+
mail.ready_to_send
|
|
28
|
+
sender = (mail['return-path'] && mail['return-path'].spec) || mail.from.first
|
|
29
|
+
destinations.each do |destination|
|
|
30
|
+
self.class.email_class.create :mail => mail.encoded, :to => destination, :from => sender
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
require 'optparse'
|
|
2
|
+
require 'net/smtp'
|
|
3
|
+
require 'smtp_tls' unless Net::SMTP.instance_methods.include?("enable_starttls_auto")
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# Hack in RSET
|
|
7
|
+
|
|
8
|
+
module Net # :nodoc:
|
|
9
|
+
class SMTP # :nodoc:
|
|
10
|
+
|
|
11
|
+
unless instance_methods.include? 'reset' then
|
|
12
|
+
##
|
|
13
|
+
# Resets the SMTP connection.
|
|
14
|
+
|
|
15
|
+
def reset
|
|
16
|
+
getok 'RSET'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# ActionMailer::ARSendmail delivers email from the email table to the
|
|
25
|
+
# SMTP server configured in your application's config/environment.rb.
|
|
26
|
+
# ar_sendmail does not work with sendmail delivery.
|
|
27
|
+
#
|
|
28
|
+
# ar_mailer can deliver to SMTP with TLS using smtp_tls.rb borrowed from Kyle
|
|
29
|
+
# Maxwell's action_mailer_optional_tls plugin. Simply set the :tls option in
|
|
30
|
+
# ActionMailer::Base's smtp_settings to true to enable TLS.
|
|
31
|
+
#
|
|
32
|
+
# See ar_sendmail -h for the full list of supported options.
|
|
33
|
+
#
|
|
34
|
+
# The interesting options are:
|
|
35
|
+
# * --daemon
|
|
36
|
+
# * --mailq
|
|
37
|
+
|
|
38
|
+
module ActionMailer; end
|
|
39
|
+
|
|
40
|
+
class ActionMailer::ARSendmail
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# The version of ActionMailer::ARSendmail you are running.
|
|
44
|
+
|
|
45
|
+
VERSION = '2.1.9'
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Maximum number of times authentication will be consecutively retried
|
|
49
|
+
|
|
50
|
+
MAX_AUTH_FAILURES = 2
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Email delivery attempts per run
|
|
54
|
+
|
|
55
|
+
attr_accessor :batch_size
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Seconds to delay between runs
|
|
59
|
+
|
|
60
|
+
attr_accessor :delay
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# Maximum age of emails in seconds before they are removed from the queue.
|
|
64
|
+
|
|
65
|
+
attr_accessor :max_age
|
|
66
|
+
|
|
67
|
+
##
|
|
68
|
+
# Be verbose
|
|
69
|
+
|
|
70
|
+
attr_accessor :verbose
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# True if only one delivery attempt will be made per call to run
|
|
75
|
+
|
|
76
|
+
attr_reader :once
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# Times authentication has failed
|
|
80
|
+
|
|
81
|
+
attr_accessor :failed_auth_count
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
# Is this email assigned to a specific worker?
|
|
85
|
+
|
|
86
|
+
attr_reader :worker
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@@pid_file = nil
|
|
92
|
+
|
|
93
|
+
def self.remove_pid_file
|
|
94
|
+
if @@pid_file
|
|
95
|
+
require 'shell'
|
|
96
|
+
sh = Shell.new
|
|
97
|
+
sh.rm @@pid_file
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
##
|
|
102
|
+
# Prints a list of unsent emails and the last delivery attempt, if any.
|
|
103
|
+
#
|
|
104
|
+
# If ActiveRecord::Timestamp is not being used the arrival time will not be
|
|
105
|
+
# known. See http://api.rubyonrails.org/classes/ActiveRecord/Timestamp.html
|
|
106
|
+
# to learn how to enable ActiveRecord::Timestamp.
|
|
107
|
+
|
|
108
|
+
def self.mailq
|
|
109
|
+
emails = ActionMailer::Base.email_class.find :all
|
|
110
|
+
|
|
111
|
+
if emails.empty? then
|
|
112
|
+
puts "Mail queue is empty"
|
|
113
|
+
return
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
total_size = 0
|
|
117
|
+
|
|
118
|
+
puts "-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------"
|
|
119
|
+
emails.each do |email|
|
|
120
|
+
size = email.mail.length
|
|
121
|
+
total_size += size
|
|
122
|
+
|
|
123
|
+
create_timestamp = email.created_on rescue
|
|
124
|
+
email.created_at rescue
|
|
125
|
+
Time.at(email.created_date) rescue # for Robot Co-op
|
|
126
|
+
nil
|
|
127
|
+
|
|
128
|
+
created = if create_timestamp.nil? then
|
|
129
|
+
' Unknown'
|
|
130
|
+
else
|
|
131
|
+
create_timestamp.strftime '%a %b %d %H:%M:%S'
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
puts "%10d %8d %s %s" % [email.id, size, created, email.from]
|
|
135
|
+
if email.last_send_attempt > 0 then
|
|
136
|
+
puts "Last send attempt: #{Time.at email.last_send_attempt}"
|
|
137
|
+
end
|
|
138
|
+
puts " #{email.to}"
|
|
139
|
+
puts
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
puts "-- #{total_size/1024} Kbytes in #{emails.length} Requests."
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
##
|
|
146
|
+
# Processes command line options in +args+
|
|
147
|
+
|
|
148
|
+
def self.process_args(args)
|
|
149
|
+
name = File.basename $0
|
|
150
|
+
|
|
151
|
+
options = {}
|
|
152
|
+
options[:Chdir] = '.'
|
|
153
|
+
options[:Daemon] = false
|
|
154
|
+
options[:Worker] = ""
|
|
155
|
+
options[:Delay] = 60
|
|
156
|
+
options[:MaxAge] = 86400 * 7
|
|
157
|
+
options[:Once] = false
|
|
158
|
+
options[:RailsEnv] = ENV['RAILS_ENV']
|
|
159
|
+
options[:Pidfile] = options[:Chdir] + '/log/ar_sendmail.pid'
|
|
160
|
+
|
|
161
|
+
opts = OptionParser.new do |opts|
|
|
162
|
+
opts.banner = "Usage: #{name} [options]"
|
|
163
|
+
opts.separator ''
|
|
164
|
+
|
|
165
|
+
opts.separator "#{name} scans the email table for new messages and sends them to the"
|
|
166
|
+
opts.separator "website's configured SMTP host."
|
|
167
|
+
opts.separator ''
|
|
168
|
+
opts.separator "#{name} must be run from a Rails application's root."
|
|
169
|
+
|
|
170
|
+
opts.separator ''
|
|
171
|
+
opts.separator 'Sendmail options:'
|
|
172
|
+
|
|
173
|
+
opts.on("-b", "--batch-size BATCH_SIZE",
|
|
174
|
+
"Maximum number of emails to send per delay",
|
|
175
|
+
"Default: Deliver all available emails", Integer) do |batch_size|
|
|
176
|
+
options[:BatchSize] = batch_size
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
opts.on( "--delay DELAY",
|
|
180
|
+
"Delay between checks for new mail",
|
|
181
|
+
"in the database",
|
|
182
|
+
"Default: #{options[:Delay]}", Integer) do |delay|
|
|
183
|
+
options[:Delay] = delay
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
opts.on( "--max-age MAX_AGE",
|
|
187
|
+
"Maxmimum age for an email. After this",
|
|
188
|
+
"it will be removed from the queue.",
|
|
189
|
+
"Set to 0 to disable queue cleanup.",
|
|
190
|
+
"Default: #{options[:MaxAge]} seconds", Integer) do |max_age|
|
|
191
|
+
options[:MaxAge] = max_age
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
opts.on("-o", "--once",
|
|
195
|
+
"Only check for new mail and deliver once",
|
|
196
|
+
"Default: #{options[:Once]}") do |once|
|
|
197
|
+
options[:Once] = once
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
opts.on("-d", "--daemonize",
|
|
201
|
+
"Run as a daemon process",
|
|
202
|
+
"Default: #{options[:Daemon]}") do |daemon|
|
|
203
|
+
options[:Daemon] = true
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
opts.on("-p", "--pidfile PIDFILE",
|
|
207
|
+
"Set the pidfile location",
|
|
208
|
+
"Default: #{options[:Chdir]}#{options[:Pidfile]}", String) do |pidfile|
|
|
209
|
+
options[:Pidfile] = pidfile
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
opts.on( "--mailq",
|
|
213
|
+
"Display a list of emails waiting to be sent") do |mailq|
|
|
214
|
+
options[:MailQ] = true
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
opts.on("-w", "--worker WORKER",
|
|
218
|
+
"Specify a worker name that the email should be processed through",
|
|
219
|
+
"Default: #{options[:Worker]}", String) do |worker|
|
|
220
|
+
options[:Worker] = worker
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
opts.separator ''
|
|
224
|
+
opts.separator 'Setup Options:'
|
|
225
|
+
|
|
226
|
+
opts.separator ''
|
|
227
|
+
opts.separator 'Generic Options:'
|
|
228
|
+
|
|
229
|
+
opts.on("-c", "--chdir PATH",
|
|
230
|
+
"Use PATH for the application path",
|
|
231
|
+
"Default: #{options[:Chdir]}") do |path|
|
|
232
|
+
usage opts, "#{path} is not a directory" unless File.directory? path
|
|
233
|
+
usage opts, "#{path} is not readable" unless File.readable? path
|
|
234
|
+
options[:Chdir] = path
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
opts.on("-e", "--environment RAILS_ENV",
|
|
238
|
+
"Set the RAILS_ENV constant",
|
|
239
|
+
"Default: #{options[:RailsEnv]}") do |env|
|
|
240
|
+
options[:RailsEnv] = env
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
opts.on("-v", "--[no-]verbose",
|
|
244
|
+
"Be verbose",
|
|
245
|
+
"Default: #{options[:Verbose]}") do |verbose|
|
|
246
|
+
options[:Verbose] = verbose
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
opts.on("-h", "--help",
|
|
250
|
+
"You're looking at it") do
|
|
251
|
+
usage opts
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
opts.on("--version", "Version of ARMailer") do
|
|
255
|
+
usage "ar_mailer #{VERSION} (adzap fork)"
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
opts.separator ''
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
opts.parse! args
|
|
262
|
+
|
|
263
|
+
ENV['RAILS_ENV'] = options[:RailsEnv]
|
|
264
|
+
|
|
265
|
+
Dir.chdir options[:Chdir] do
|
|
266
|
+
begin
|
|
267
|
+
require 'config/environment'
|
|
268
|
+
require 'action_mailer/ar_mailer'
|
|
269
|
+
rescue LoadError
|
|
270
|
+
usage opts, <<-EOF
|
|
271
|
+
#{name} must be run from a Rails application's root to deliver email.
|
|
272
|
+
#{Dir.pwd} does not appear to be a Rails application root.
|
|
273
|
+
EOF
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
return options
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
##
|
|
281
|
+
# Processes +args+ and runs as appropriate
|
|
282
|
+
|
|
283
|
+
def self.run(args = ARGV)
|
|
284
|
+
options = process_args args
|
|
285
|
+
|
|
286
|
+
if options.include? :MailQ then
|
|
287
|
+
mailq
|
|
288
|
+
exit
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
if options[:Daemon] then
|
|
292
|
+
require 'webrick/server'
|
|
293
|
+
@@pid_file = File.expand_path(options[:Pidfile], options[:Chdir])
|
|
294
|
+
if File.exists? @@pid_file
|
|
295
|
+
# check to see if process is actually running
|
|
296
|
+
pid = ''
|
|
297
|
+
File.open(@@pid_file, 'r') {|f| pid = f.read.chomp }
|
|
298
|
+
if system("ps -p #{pid} | grep #{pid}") # returns true if process is running, o.w. false
|
|
299
|
+
$stderr.puts "Warning: The pid file #{@@pid_file} exists and ar_sendmail is running. Shutting down."
|
|
300
|
+
exit -1
|
|
301
|
+
else
|
|
302
|
+
# not running, so remove existing pid file and continue
|
|
303
|
+
self.remove_pid_file
|
|
304
|
+
$stderr.puts "ar_sendmail is not running. Removing existing pid file and starting up..."
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
WEBrick::Daemon.start
|
|
308
|
+
File.open(@@pid_file, 'w') {|f| f.write("#{Process.pid}\n")}
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
new(options).run
|
|
312
|
+
|
|
313
|
+
rescue SystemExit
|
|
314
|
+
raise
|
|
315
|
+
rescue SignalException
|
|
316
|
+
exit
|
|
317
|
+
rescue Exception => e
|
|
318
|
+
$stderr.puts "Unhandled exception #{e.message}(#{e.class}):"
|
|
319
|
+
$stderr.puts "\t#{e.backtrace.join "\n\t"}"
|
|
320
|
+
exit -2
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
##
|
|
324
|
+
# Prints a usage message to $stderr using +opts+ and exits
|
|
325
|
+
|
|
326
|
+
def self.usage(opts, message = nil)
|
|
327
|
+
if message then
|
|
328
|
+
$stderr.puts message
|
|
329
|
+
$stderr.puts
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
$stderr.puts opts
|
|
333
|
+
exit 1
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
##
|
|
337
|
+
# Creates a new ARSendmail.
|
|
338
|
+
#
|
|
339
|
+
# Valid options are:
|
|
340
|
+
# <tt>:BatchSize</tt>:: Maximum number of emails to send per delay
|
|
341
|
+
# <tt>:Delay</tt>:: Delay between deliver attempts
|
|
342
|
+
# <tt>:Once</tt>:: Only attempt to deliver emails once when run is called
|
|
343
|
+
# <tt>:Verbose</tt>:: Be verbose.
|
|
344
|
+
|
|
345
|
+
def initialize(options = {})
|
|
346
|
+
options[:Delay] ||= 60
|
|
347
|
+
options[:MaxAge] ||= 86400 * 7
|
|
348
|
+
options[:Worker] ||= ""
|
|
349
|
+
|
|
350
|
+
@batch_size = options[:BatchSize]
|
|
351
|
+
@delay = options[:Delay]
|
|
352
|
+
@once = options[:Once]
|
|
353
|
+
@verbose = options[:Verbose]
|
|
354
|
+
@max_age = options[:MaxAge]
|
|
355
|
+
@worker = options[:Worker]
|
|
356
|
+
|
|
357
|
+
@failed_auth_count = 0
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
##
|
|
361
|
+
# Removes emails that have lived in the queue for too long. If max_age is
|
|
362
|
+
# set to 0, no emails will be removed.
|
|
363
|
+
|
|
364
|
+
def cleanup
|
|
365
|
+
return if @max_age == 0
|
|
366
|
+
timeout = Time.now - @max_age
|
|
367
|
+
conditions = ['last_send_attempt > 0 and created_on < ?', timeout]
|
|
368
|
+
mail = ActionMailer::Base.email_class.destroy_all conditions
|
|
369
|
+
|
|
370
|
+
log "expired #{mail.length} emails from the queue"
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
##
|
|
374
|
+
# Delivers +emails+ to ActionMailer's SMTP server and destroys them.
|
|
375
|
+
|
|
376
|
+
def deliver(emails)
|
|
377
|
+
settings = [
|
|
378
|
+
smtp_settings[:domain],
|
|
379
|
+
(smtp_settings[:user] || smtp_settings[:user_name]),
|
|
380
|
+
smtp_settings[:password],
|
|
381
|
+
smtp_settings[:authentication]
|
|
382
|
+
]
|
|
383
|
+
|
|
384
|
+
smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port])
|
|
385
|
+
if smtp.respond_to?(:enable_starttls_auto)
|
|
386
|
+
smtp.enable_starttls_auto unless smtp_settings[:tls] == false
|
|
387
|
+
else
|
|
388
|
+
settings << smtp_settings[:tls]
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
smtp.start(*settings) do |session|
|
|
392
|
+
@failed_auth_count = 0
|
|
393
|
+
until emails.empty? do
|
|
394
|
+
email = emails.shift
|
|
395
|
+
begin
|
|
396
|
+
res = session.send_message email.mail, email.from, email.to
|
|
397
|
+
email.destroy
|
|
398
|
+
log "sent email %011d from %s to %s: %p" %
|
|
399
|
+
[email.id, email.from, email.to, res]
|
|
400
|
+
rescue Net::SMTPFatalError => e
|
|
401
|
+
log "5xx error sending email %d, removing from queue: %p(%s):\n\t%s" %
|
|
402
|
+
[email.id, e.message, e.class, e.backtrace.join("\n\t")]
|
|
403
|
+
email.destroy
|
|
404
|
+
session.reset
|
|
405
|
+
rescue Net::SMTPServerBusy => e
|
|
406
|
+
log "server too busy, stopping delivery cycle"
|
|
407
|
+
return
|
|
408
|
+
rescue Net::SMTPUnknownError, Net::SMTPSyntaxError, TimeoutError, Timeout::Error => e
|
|
409
|
+
email.last_send_attempt = Time.now.to_i
|
|
410
|
+
email.save rescue nil
|
|
411
|
+
log "error sending email %d: %p(%s):\n\t%s" %
|
|
412
|
+
[email.id, e.message, e.class, e.backtrace.join("\n\t")]
|
|
413
|
+
session.reset
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
rescue Net::SMTPAuthenticationError => e
|
|
418
|
+
@failed_auth_count += 1
|
|
419
|
+
if @failed_auth_count >= MAX_AUTH_FAILURES then
|
|
420
|
+
log "authentication error, giving up: #{e.message}"
|
|
421
|
+
raise e
|
|
422
|
+
else
|
|
423
|
+
log "authentication error, retrying: #{e.message}"
|
|
424
|
+
end
|
|
425
|
+
sleep delay
|
|
426
|
+
rescue Net::SMTPServerBusy, SystemCallError, OpenSSL::SSL::SSLError
|
|
427
|
+
# ignore SMTPServerBusy/EPIPE/ECONNRESET from Net::SMTP.start's ensure
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
##
|
|
431
|
+
# Prepares ar_sendmail for exiting
|
|
432
|
+
|
|
433
|
+
def do_exit
|
|
434
|
+
log "caught signal, shutting down"
|
|
435
|
+
self.class.remove_pid_file
|
|
436
|
+
exit 130
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
##
|
|
440
|
+
# Returns emails in email_class that haven't had a delivery attempt in the
|
|
441
|
+
# last 300 seconds.
|
|
442
|
+
|
|
443
|
+
def find_emails
|
|
444
|
+
options = { :conditions => ['last_send_attempt < ? AND worker=?', Time.now.to_i - 300, worker.to_s] }
|
|
445
|
+
options[:limit] = batch_size unless batch_size.nil?
|
|
446
|
+
mail = ActionMailer::Base.email_class.find :all, options
|
|
447
|
+
|
|
448
|
+
log "found #{mail.length} emails to send"
|
|
449
|
+
mail
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
##
|
|
453
|
+
# Installs signal handlers to gracefully exit.
|
|
454
|
+
|
|
455
|
+
def install_signal_handlers
|
|
456
|
+
trap 'TERM' do do_exit end
|
|
457
|
+
trap 'INT' do do_exit end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
##
|
|
461
|
+
# Logs +message+ if verbose
|
|
462
|
+
|
|
463
|
+
def log(message)
|
|
464
|
+
$stderr.puts message if @verbose
|
|
465
|
+
ActionMailer::Base.logger.info "ar_sendmail: #{message}"
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
##
|
|
469
|
+
# Scans for emails and delivers them every delay seconds. Only returns if
|
|
470
|
+
# once is true.
|
|
471
|
+
|
|
472
|
+
def run
|
|
473
|
+
install_signal_handlers
|
|
474
|
+
|
|
475
|
+
loop do
|
|
476
|
+
begin
|
|
477
|
+
cleanup
|
|
478
|
+
emails = find_emails
|
|
479
|
+
deliver(emails) unless emails.empty?
|
|
480
|
+
rescue ActiveRecord::Transactions::TransactionError
|
|
481
|
+
end
|
|
482
|
+
break if @once
|
|
483
|
+
sleep @delay
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
##
|
|
488
|
+
# Proxy to ActionMailer::Base::smtp_settings. See
|
|
489
|
+
# http://api.rubyonrails.org/classes/ActionMailer/Base.html
|
|
490
|
+
# for instructions on how to configure ActionMailer's SMTP server.
|
|
491
|
+
#
|
|
492
|
+
# Falls back to ::server_settings if ::smtp_settings doesn't exist for
|
|
493
|
+
# backwards compatibility.
|
|
494
|
+
|
|
495
|
+
def smtp_settings
|
|
496
|
+
ActionMailer::Base.smtp_settings rescue ActionMailer::Base.server_settings
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
end
|