foresth-ar_mailer 2.1.8

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