adzap-ar_mailer 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,57 @@
1
+ = 1.4.0
2
+
3
+ * New Features
4
+ * Added pid file creation on daemonize with command line option to specify pid filename [Dylan Egan]
5
+
6
+ = 1.3.1
7
+
8
+ * Fix bug #12530, gmail causes SSL errors. Submitted by Kyle Maxwell
9
+ and Alex Ostleitner.
10
+ * Try ActionMailer::Base::server_settings then ::smtp_settings. Fixes
11
+ bug #12516. Submitted by Alex Ostleitner.
12
+
13
+ = 1.3.0
14
+
15
+ * New Features
16
+ * Added automatic mail queue cleanup.
17
+ * MAY CAUSE LOSS OF DATA. If you haven't run ar_sendmail within
18
+ the expiry time, set it to 0.
19
+ * Bugs fixed
20
+ * Authentication errors are now handled by retrying once.
21
+
22
+ = 1.2.0
23
+
24
+ * Bugs fixed
25
+ * Handle SMTPServerBusy by backing off @delay seconds then re-queueing
26
+ * Allow email delivery class to be set in ARMailer.
27
+ * ar_sendmail --mailq works with --table-name now.
28
+ * Miscellaneous Updates
29
+ * Added documentation to require 'action_mailer/ar_mailer' in
30
+ instructions.
31
+ * Moved to ZSS p4 repository
32
+ * Supports TLS now. Requested by Dave Thomas. smtp_tls.rb from Kyle
33
+ Maxwell & etc.
34
+
35
+ = 1.1.0
36
+
37
+ * Features
38
+ * Added --chdir to set rails directory
39
+ * Added --environment to set RAILS_ENV
40
+ * Exits cleanly on TERM or INT signals
41
+ * Added FreeBSD rc.d script
42
+ * Exceptions during SMTP sending are now logged
43
+ * No longer waits if sending email took too long
44
+ * Bugs fixed
45
+ * Fixed last send attempt in --mailq
46
+ * Better SMTP error handling
47
+ * Messages are removed from the queue on 5xx errors
48
+ * Added Net::SMTP.reset to avoid needing to recreate the connection
49
+
50
+ = 1.0.1
51
+
52
+ * Bugs fixed
53
+ * From and to of email destination were swapped
54
+
55
+ = 1.0.0
56
+
57
+ * Birthday
data/LICENSE.txt ADDED
@@ -0,0 +1,28 @@
1
+ Original code copyright 2006, 2007, Eric Hodel, The Robot Co-op. All
2
+ rights reserved. Some code under other license, see individual files
3
+ for details.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions
7
+ are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+ 2. Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+ 3. Neither the names of the authors nor the names of their contributors
15
+ may be used to endorse or promote products derived from this software
16
+ without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
19
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
22
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
23
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
24
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
25
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Manifest.txt ADDED
@@ -0,0 +1,13 @@
1
+ History.txt
2
+ LICENSE.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ bin/ar_sendmail
7
+ lib/action_mailer/ar_mailer.rb
8
+ lib/action_mailer/ar_sendmail.rb
9
+ lib/smtp_tls.rb
10
+ share/ar_sendmail
11
+ test/action_mailer.rb
12
+ test/test_armailer.rb
13
+ test/test_arsendmail.rb
data/README.txt ADDED
@@ -0,0 +1,39 @@
1
+ = ar_mailer
2
+
3
+ A two-phase delivery agent for ActionMailer
4
+
5
+ Rubyforge Project:
6
+
7
+ http://rubyforge.org/projects/seattlerb
8
+
9
+ Documentation:
10
+
11
+ http://seattlerb.org/ar_mailer
12
+
13
+ Bugs:
14
+
15
+ http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921
16
+
17
+ == About
18
+
19
+ Even delivering email to the local machine may take too long when you have to
20
+ send hundreds of messages. ar_mailer allows you to store messages into the
21
+ database for later delivery by a separate process, ar_sendmail.
22
+
23
+ == Installing ar_mailer
24
+
25
+ Just install the gem:
26
+
27
+ $ sudo gem install ar_mailer
28
+
29
+ See ActionMailer::ARMailer for instructions on converting to ARMailer.
30
+
31
+ See ar_sendmail -h for options to ar_sendmail.
32
+
33
+ NOTE: You may need to delete an smtp_tls.rb file if you have one lying
34
+ around. ar_mailer supplies it own.
35
+
36
+ === ar_sendmail on FreeBSD or NetBSD
37
+
38
+ An rc.d script is included in share/ar_sendmail.
39
+
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'hoe'
2
+
3
+ require './lib/action_mailer/ar_sendmail'
4
+
5
+ Hoe.new 'ar_mailer', ActionMailer::ARSendmail::VERSION do |s|
6
+ s.rubyforge_name = 'seattlerb'
7
+ s.summary = s.paragraphs_of('README.txt', 1).join(' ')
8
+ s.description = s.paragraphs_of('README.txt', 9).join(' ')
9
+ s.url = s.paragraphs_of('README.txt', 5).join(' ')
10
+ s.author = 'Eric Hodel'
11
+ s.email = 'drbrain@segment7.net'
12
+ s.changes = s.paragraphs_of('History.txt', 0..1).join("\n\n")
13
+ end
14
+
data/bin/ar_sendmail ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'action_mailer/ar_sendmail'
4
+
5
+ ActionMailer::ARSendmail.run
6
+
@@ -0,0 +1,98 @@
1
+ require 'action_mailer'
2
+
3
+ ##
4
+ # Adds sending email through an ActiveRecord table as a delivery method for
5
+ # ActionMailer.
6
+ #
7
+ # == Converting to ActionMailer::ARMailer
8
+ #
9
+ # Go to your Rails project:
10
+ #
11
+ # $ cd your_rails_project
12
+ #
13
+ # Create a new migration:
14
+ #
15
+ # $ ar_sendmail --create-migration
16
+ #
17
+ # You'll need to redirect this into a file. If you want a different name
18
+ # provide the --table-name option.
19
+ #
20
+ # Create a new model:
21
+ #
22
+ # $ ar_sendmail --create-model
23
+ #
24
+ # You'll need to redirect this into a file. If you want a different name
25
+ # provide the --table-name option.
26
+ #
27
+ # Change your email classes to inherit from ActionMailer::ARMailer instead of
28
+ # ActionMailer::Base:
29
+ #
30
+ # --- app/model/emailer.rb.orig 2006-08-10 13:16:33.000000000 -0700
31
+ # +++ app/model/emailer.rb 2006-08-10 13:16:43.000000000 -0700
32
+ # @@ -1,4 +1,4 @@
33
+ # -class Emailer < ActionMailer::Base
34
+ # +class Emailer < ActionMailer::ARMailer
35
+ #
36
+ # def comment_notification(comment)
37
+ # from comment.author.email
38
+ #
39
+ # You'll need to be sure to set the From address for your emails. Something
40
+ # like:
41
+ #
42
+ # def list_send(recipient)
43
+ # from 'no_reply@example.com'
44
+ # # ...
45
+ #
46
+ # Edit config/environment.rb and require ar_mailer.rb:
47
+ #
48
+ # require 'action_mailer/ar_mailer'
49
+ #
50
+ # Edit config/environments/production.rb and set the delivery agent:
51
+ #
52
+ # $ grep delivery_method config/environments/production.rb
53
+ # ActionMailer::Base.delivery_method = :activerecord
54
+ #
55
+ # Run ar_sendmail:
56
+ #
57
+ # $ ar_sendmail
58
+ #
59
+ # You can also run it from cron with -o, or as a daemon with -d.
60
+ #
61
+ # See <tt>ar_sendmail -h</tt> for full details.
62
+ #
63
+ # == Alternate Mail Storage
64
+ #
65
+ # If you want to set the ActiveRecord model that emails will be stored in,
66
+ # see ActionMailer::ARMailer::email_class=
67
+
68
+ class ActionMailer::ARMailer < ActionMailer::Base
69
+
70
+ @@email_class = Email
71
+
72
+ ##
73
+ # Current email class for deliveries.
74
+
75
+ def self.email_class
76
+ @@email_class
77
+ end
78
+
79
+ ##
80
+ # Sets the email class for deliveries.
81
+
82
+ def self.email_class=(klass)
83
+ @@email_class = klass
84
+ end
85
+
86
+ ##
87
+ # Adds +mail+ to the Email table. Only the first From address for +mail+ is
88
+ # used.
89
+
90
+ def perform_delivery_activerecord(mail)
91
+ mail.destinations.each do |destination|
92
+ @@email_class.create :mail => mail.encoded, :to => destination,
93
+ :from => mail.from.first
94
+ end
95
+ end
96
+
97
+ end
98
+
@@ -0,0 +1,554 @@
1
+ require 'optparse'
2
+ require 'net/smtp'
3
+ require 'smtp_tls'
4
+ require 'rubygems'
5
+
6
+ class Object # :nodoc:
7
+ unless respond_to? :path2class then
8
+ def self.path2class(path)
9
+ path.split(/::/).inject self do |k,n| k.const_get n end
10
+ end
11
+ end
12
+ end
13
+
14
+ ##
15
+ # Hack in RSET
16
+
17
+ module Net # :nodoc:
18
+ class SMTP # :nodoc:
19
+
20
+ unless instance_methods.include? 'reset' then
21
+ ##
22
+ # Resets the SMTP connection.
23
+
24
+ def reset
25
+ getok 'RSET'
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ module ActionMailer; end # :nodoc:
33
+
34
+ ##
35
+ # ActionMailer::ARSendmail delivers email from the email table to the
36
+ # SMTP server configured in your application's config/environment.rb.
37
+ # ar_sendmail does not work with sendmail delivery.
38
+ #
39
+ # ar_mailer can deliver to SMTP with TLS using smtp_tls.rb borrowed from Kyle
40
+ # Maxwell's action_mailer_optional_tls plugin. Simply set the :tls option in
41
+ # ActionMailer::Base's smtp_settings to true to enable TLS.
42
+ #
43
+ # See ar_sendmail -h for the full list of supported options.
44
+ #
45
+ # The interesting options are:
46
+ # * --daemon
47
+ # * --mailq
48
+ # * --create-migration
49
+ # * --create-model
50
+ # * --table-name
51
+
52
+ class ActionMailer::ARSendmail
53
+
54
+ ##
55
+ # The version of ActionMailer::ARSendmail you are running.
56
+
57
+ VERSION = '1.3.1'
58
+
59
+ ##
60
+ # Maximum number of times authentication will be consecutively retried
61
+
62
+ MAX_AUTH_FAILURES = 2
63
+
64
+ ##
65
+ # Email delivery attempts per run
66
+
67
+ attr_accessor :batch_size
68
+
69
+ ##
70
+ # Seconds to delay between runs
71
+
72
+ attr_accessor :delay
73
+
74
+ ##
75
+ # Maximum age of emails in seconds before they are removed from the queue.
76
+
77
+ attr_accessor :max_age
78
+
79
+ ##
80
+ # Be verbose
81
+
82
+ attr_accessor :verbose
83
+
84
+ ##
85
+ # ActiveRecord class that holds emails
86
+
87
+ attr_reader :email_class
88
+
89
+ ##
90
+ # True if only one delivery attempt will be made per call to run
91
+
92
+ attr_reader :once
93
+
94
+ ##
95
+ # Times authentication has failed
96
+
97
+ attr_accessor :failed_auth_count
98
+
99
+ @@pid_file = nil
100
+
101
+ def self.remove_pid_file
102
+ if @@pid_file
103
+ require 'shell'
104
+ sh = Shell.new
105
+ sh.rm @@pid_file
106
+ end
107
+ end
108
+
109
+ ##
110
+ # Creates a new migration using +table_name+ and prints it on stdout.
111
+
112
+ def self.create_migration(table_name)
113
+ require 'active_support'
114
+ puts <<-EOF
115
+ class Add#{table_name.classify} < ActiveRecord::Migration
116
+ def self.up
117
+ create_table :#{table_name.tableize} do |t|
118
+ t.column :from, :string
119
+ t.column :to, :string
120
+ t.column :last_send_attempt, :integer, :default => 0
121
+ t.column :mail, :text
122
+ t.column :created_on, :datetime
123
+ end
124
+ end
125
+
126
+ def self.down
127
+ drop_table :#{table_name.tableize}
128
+ end
129
+ end
130
+ EOF
131
+ end
132
+
133
+ ##
134
+ # Creates a new model using +table_name+ and prints it on stdout.
135
+
136
+ def self.create_model(table_name)
137
+ require 'active_support'
138
+ puts <<-EOF
139
+ class #{table_name.classify} < ActiveRecord::Base
140
+ end
141
+ EOF
142
+ end
143
+
144
+ ##
145
+ # Prints a list of unsent emails and the last delivery attempt, if any.
146
+ #
147
+ # If ActiveRecord::Timestamp is not being used the arrival time will not be
148
+ # known. See http://api.rubyonrails.org/classes/ActiveRecord/Timestamp.html
149
+ # to learn how to enable ActiveRecord::Timestamp.
150
+
151
+ def self.mailq(table_name)
152
+ klass = table_name.split('::').inject(Object) { |k,n| k.const_get n }
153
+ emails = klass.find :all
154
+
155
+ if emails.empty? then
156
+ puts "Mail queue is empty"
157
+ return
158
+ end
159
+
160
+ total_size = 0
161
+
162
+ puts "-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------"
163
+ emails.each do |email|
164
+ size = email.mail.length
165
+ total_size += size
166
+
167
+ create_timestamp = email.created_on rescue
168
+ email.created_at rescue
169
+ Time.at(email.created_date) rescue # for Robot Co-op
170
+ nil
171
+
172
+ created = if create_timestamp.nil? then
173
+ ' Unknown'
174
+ else
175
+ create_timestamp.strftime '%a %b %d %H:%M:%S'
176
+ end
177
+
178
+ puts "%10d %8d %s %s" % [email.id, size, created, email.from]
179
+ if email.last_send_attempt > 0 then
180
+ puts "Last send attempt: #{Time.at email.last_send_attempt}"
181
+ end
182
+ puts " #{email.to}"
183
+ puts
184
+ end
185
+
186
+ puts "-- #{total_size/1024} Kbytes in #{emails.length} Requests."
187
+ end
188
+
189
+ ##
190
+ # Processes command line options in +args+
191
+
192
+ def self.process_args(args)
193
+ name = File.basename $0
194
+
195
+ options = {}
196
+ options[:Chdir] = '.'
197
+ options[:Daemon] = false
198
+ options[:Delay] = 60
199
+ options[:MaxAge] = 86400 * 7
200
+ options[:Once] = false
201
+ options[:RailsEnv] = ENV['RAILS_ENV']
202
+ options[:TableName] = 'Email'
203
+ options[:Pidfile] = options[:Chdir] + '/log/ar_sendmail.pid'
204
+
205
+ opts = OptionParser.new do |opts|
206
+ opts.banner = "Usage: #{name} [options]"
207
+ opts.separator ''
208
+
209
+ opts.separator "#{name} scans the email table for new messages and sends them to the"
210
+ opts.separator "website's configured SMTP host."
211
+ opts.separator ''
212
+ opts.separator "#{name} must be run from a Rails application's root."
213
+
214
+ opts.separator ''
215
+ opts.separator 'Sendmail options:'
216
+
217
+ opts.on("-b", "--batch-size BATCH_SIZE",
218
+ "Maximum number of emails to send per delay",
219
+ "Default: Deliver all available emails", Integer) do |batch_size|
220
+ options[:BatchSize] = batch_size
221
+ end
222
+
223
+ opts.on( "--delay DELAY",
224
+ "Delay between checks for new mail",
225
+ "in the database",
226
+ "Default: #{options[:Delay]}", Integer) do |delay|
227
+ options[:Delay] = delay
228
+ end
229
+
230
+ opts.on( "--max-age MAX_AGE",
231
+ "Maxmimum age for an email. After this",
232
+ "it will be removed from the queue.",
233
+ "Set to 0 to disable queue cleanup.",
234
+ "Default: #{options[:MaxAge]} seconds", Integer) do |max_age|
235
+ options[:MaxAge] = max_age
236
+ end
237
+
238
+ opts.on("-o", "--once",
239
+ "Only check for new mail and deliver once",
240
+ "Default: #{options[:Once]}") do |once|
241
+ options[:Once] = once
242
+ end
243
+
244
+ opts.on("-d", "--daemonize",
245
+ "Run as a daemon process",
246
+ "Default: #{options[:Daemon]}") do |daemon|
247
+ options[:Daemon] = true
248
+ end
249
+
250
+ opts.on("-p", "--pidfile PIDFILE",
251
+ "Set the pidfile location",
252
+ "Default: #{options[:Chdir]}#{options[:Pidfile]}", String) do |pidfile|
253
+ options[:Pidfile] = pidfile
254
+ end
255
+
256
+ opts.on( "--mailq",
257
+ "Display a list of emails waiting to be sent") do |mailq|
258
+ options[:MailQ] = true
259
+ end
260
+
261
+ opts.separator ''
262
+ opts.separator 'Setup Options:'
263
+
264
+ opts.on( "--create-migration",
265
+ "Prints a migration to add an Email table",
266
+ "to stdout") do |create|
267
+ options[:Migrate] = true
268
+ end
269
+
270
+ opts.on( "--create-model",
271
+ "Prints a model for an Email ActiveRecord",
272
+ "object to stdout") do |create|
273
+ options[:Model] = true
274
+ end
275
+
276
+ opts.separator ''
277
+ opts.separator 'Generic Options:'
278
+
279
+ opts.on("-c", "--chdir PATH",
280
+ "Use PATH for the application path",
281
+ "Default: #{options[:Chdir]}") do |path|
282
+ usage opts, "#{path} is not a directory" unless File.directory? path
283
+ usage opts, "#{path} is not readable" unless File.readable? path
284
+ options[:Chdir] = path
285
+ end
286
+
287
+ opts.on("-e", "--environment RAILS_ENV",
288
+ "Set the RAILS_ENV constant",
289
+ "Default: #{options[:RailsEnv]}") do |env|
290
+ options[:RailsEnv] = env
291
+ end
292
+
293
+ opts.on("-t", "--table-name TABLE_NAME",
294
+ "Name of table holding emails",
295
+ "Used for both sendmail and",
296
+ "migration creation",
297
+ "Default: #{options[:TableName]}") do |name|
298
+ options[:TableName] = name
299
+ end
300
+
301
+ opts.on("-v", "--[no-]verbose",
302
+ "Be verbose",
303
+ "Default: #{options[:Verbose]}") do |verbose|
304
+ options[:Verbose] = verbose
305
+ end
306
+
307
+ opts.on("-h", "--help",
308
+ "You're looking at it") do
309
+ usage opts
310
+ end
311
+
312
+ opts.separator ''
313
+ end
314
+
315
+ opts.parse! args
316
+
317
+ return options if options.include? :Migrate or options.include? :Model
318
+
319
+ ENV['RAILS_ENV'] = options[:RailsEnv]
320
+
321
+ Dir.chdir options[:Chdir] do
322
+ begin
323
+ require 'config/environment'
324
+ rescue LoadError
325
+ usage opts, <<-EOF
326
+ #{name} must be run from a Rails application's root to deliver email.
327
+ #{Dir.pwd} does not appear to be a Rails application root.
328
+ EOF
329
+ end
330
+ end
331
+
332
+ return options
333
+ end
334
+
335
+ ##
336
+ # Processes +args+ and runs as appropriate
337
+
338
+ def self.run(args = ARGV)
339
+ options = process_args args
340
+
341
+ if options.include? :Migrate then
342
+ create_migration options[:TableName]
343
+ exit
344
+ elsif options.include? :Model then
345
+ create_model options[:TableName]
346
+ exit
347
+ elsif options.include? :MailQ then
348
+ mailq options[:TableName]
349
+ exit
350
+ end
351
+
352
+ if options[:Daemon] then
353
+ require 'webrick/server'
354
+ @@pid_file = options[:Pidfile]
355
+ if File.exists? @@pid_file
356
+ # check to see if process is actually running
357
+ pid = ''
358
+ File.open(@@pid_file, 'r') {|f| pid = f.read.chomp }
359
+ if system("ps -p #{pid} | grep #{pid}") # returns true if process is running, o.w. false
360
+ $stderr.puts "Warning: The pid file #{@@pid_file} exists and ar_sendmail is running. Shutting down."
361
+ exit
362
+ else
363
+ # not running, so remove existing pid file and continue
364
+ self.remove_pid_file
365
+ log "ar_sendmail is not running. Removing existing pid file and starting up..."
366
+ end
367
+ end
368
+ WEBrick::Daemon.start
369
+ File.open(@@pid_file, 'w') {|f| f.write("#{Process.pid}\n")}
370
+ end
371
+
372
+ new(options).run
373
+
374
+ rescue SystemExit
375
+ raise
376
+ rescue SignalException
377
+ exit
378
+ rescue Exception => e
379
+ $stderr.puts "Unhandled exception #{e.message}(#{e.class}):"
380
+ $stderr.puts "\t#{e.backtrace.join "\n\t"}"
381
+ exit 1
382
+ end
383
+
384
+ ##
385
+ # Prints a usage message to $stderr using +opts+ and exits
386
+
387
+ def self.usage(opts, message = nil)
388
+ if message then
389
+ $stderr.puts message
390
+ $stderr.puts
391
+ end
392
+
393
+ $stderr.puts opts
394
+ exit 1
395
+ end
396
+
397
+ ##
398
+ # Creates a new ARSendmail.
399
+ #
400
+ # Valid options are:
401
+ # <tt>:BatchSize</tt>:: Maximum number of emails to send per delay
402
+ # <tt>:Delay</tt>:: Delay between deliver attempts
403
+ # <tt>:TableName</tt>:: Table name that stores the emails
404
+ # <tt>:Once</tt>:: Only attempt to deliver emails once when run is called
405
+ # <tt>:Verbose</tt>:: Be verbose.
406
+
407
+ def initialize(options = {})
408
+ options[:Delay] ||= 60
409
+ options[:TableName] ||= 'Email'
410
+ options[:MaxAge] ||= 86400 * 7
411
+
412
+ @batch_size = options[:BatchSize]
413
+ @delay = options[:Delay]
414
+ @email_class = Object.path2class options[:TableName]
415
+ @once = options[:Once]
416
+ @verbose = options[:Verbose]
417
+ @max_age = options[:MaxAge]
418
+
419
+ @failed_auth_count = 0
420
+ end
421
+
422
+ ##
423
+ # Removes emails that have lived in the queue for too long. If max_age is
424
+ # set to 0, no emails will be removed.
425
+
426
+ def cleanup
427
+ return if @max_age == 0
428
+ timeout = Time.now - @max_age
429
+ conditions = ['last_send_attempt > 0 and created_on < ?', timeout]
430
+ mail = @email_class.destroy_all conditions
431
+
432
+ log "expired #{mail.length} emails from the queue"
433
+ end
434
+
435
+ ##
436
+ # Delivers +emails+ to ActionMailer's SMTP server and destroys them.
437
+
438
+ def deliver(emails)
439
+ user = smtp_settings[:user] || smtp_settings[:user_name]
440
+ Net::SMTP.start smtp_settings[:address], smtp_settings[:port],
441
+ smtp_settings[:domain], user,
442
+ smtp_settings[:password],
443
+ smtp_settings[:authentication],
444
+ smtp_settings[:tls] do |smtp|
445
+ @failed_auth_count = 0
446
+ until emails.empty? do
447
+ email = emails.shift
448
+ begin
449
+ res = smtp.send_message email.mail, email.from, email.to
450
+ email.destroy
451
+ log "sent email %011d from %s to %s: %p" %
452
+ [email.id, email.from, email.to, res]
453
+ rescue Net::SMTPFatalError => e
454
+ log "5xx error sending email %d, removing from queue: %p(%s):\n\t%s" %
455
+ [email.id, e.message, e.class, e.backtrace.join("\n\t")]
456
+ email.destroy
457
+ smtp.reset
458
+ rescue Net::SMTPServerBusy => e
459
+ log "server too busy, sleeping #{@delay} seconds"
460
+ sleep delay
461
+ return
462
+ rescue Net::SMTPUnknownError, Net::SMTPSyntaxError, TimeoutError => e
463
+ email.last_send_attempt = Time.now.to_i
464
+ email.save rescue nil
465
+ log "error sending email %d: %p(%s):\n\t%s" %
466
+ [email.id, e.message, e.class, e.backtrace.join("\n\t")]
467
+ smtp.reset
468
+ end
469
+ end
470
+ end
471
+ rescue Net::SMTPAuthenticationError => e
472
+ @failed_auth_count += 1
473
+ if @failed_auth_count >= MAX_AUTH_FAILURES then
474
+ log "authentication error, giving up: #{e.message}"
475
+ raise e
476
+ else
477
+ log "authentication error, retrying: #{e.message}"
478
+ end
479
+ sleep delay
480
+ rescue Net::SMTPServerBusy, SystemCallError, OpenSSL::SSL::SSLError
481
+ # ignore SMTPServerBusy/EPIPE/ECONNRESET from Net::SMTP.start's ensure
482
+ end
483
+
484
+ ##
485
+ # Prepares ar_sendmail for exiting
486
+
487
+ def do_exit
488
+ log "caught signal, shutting down"
489
+ self.class.remove_pid_file
490
+ exit
491
+ end
492
+
493
+ ##
494
+ # Returns emails in email_class that haven't had a delivery attempt in the
495
+ # last 300 seconds.
496
+
497
+ def find_emails
498
+ options = { :conditions => ['last_send_attempt < ?', Time.now.to_i - 300] }
499
+ options[:limit] = batch_size unless batch_size.nil?
500
+ mail = @email_class.find :all, options
501
+
502
+ log "found #{mail.length} emails to send"
503
+ mail
504
+ end
505
+
506
+ ##
507
+ # Installs signal handlers to gracefully exit.
508
+
509
+ def install_signal_handlers
510
+ trap 'TERM' do do_exit end
511
+ trap 'INT' do do_exit end
512
+ end
513
+
514
+ ##
515
+ # Logs +message+ if verbose
516
+
517
+ def log(message)
518
+ $stderr.puts message if @verbose
519
+ ActionMailer::Base.logger.info "ar_sendmail: #{message}"
520
+ end
521
+
522
+ ##
523
+ # Scans for emails and delivers them every delay seconds. Only returns if
524
+ # once is true.
525
+
526
+ def run
527
+ install_signal_handlers
528
+
529
+ loop do
530
+ now = Time.now
531
+ begin
532
+ cleanup
533
+ deliver find_emails
534
+ rescue ActiveRecord::Transactions::TransactionError
535
+ end
536
+ break if @once
537
+ sleep @delay if now + @delay > Time.now
538
+ end
539
+ end
540
+
541
+ ##
542
+ # Proxy to ActionMailer::Base::smtp_settings. See
543
+ # http://api.rubyonrails.org/classes/ActionMailer/Base.html
544
+ # for instructions on how to configure ActionMailer's SMTP server.
545
+ #
546
+ # Falls back to ::server_settings if ::smtp_settings doesn't exist for
547
+ # backwards compatibility.
548
+
549
+ def smtp_settings
550
+ ActionMailer::Base.smtp_settings rescue ActionMailer::Base.server_settings
551
+ end
552
+
553
+ end
554
+