ar_mailer 1.2.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,19 @@
1
+ = 1.3.1
2
+
3
+ * Fix bug #12530, gmail causes SSL errors. Submitted by Kyle Maxwell
4
+ and Alex Ostleitner.
5
+ * Try ActionMailer::Base::server_settings then ::smtp_settings. Fixes
6
+ bug #12516. Submitted by Alex Ostleitner.
7
+
8
+ = 1.3.0
9
+
10
+ * New Features
11
+ * Added automatic mail queue cleanup.
12
+ * MAY CAUSE LOSS OF DATA. If you haven't run ar_sendmail within
13
+ the expiry time, set it to 0.
14
+ * Bugs fixed
15
+ * Authentication errors are now handled by retrying once.
16
+
1
17
  = 1.2.0
2
18
 
3
19
  * Bugs fixed
data/README.txt CHANGED
@@ -16,7 +16,7 @@ http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921
16
16
 
17
17
  == About
18
18
 
19
- Even deliviring email to the local machine may take too long when you have to
19
+ Even delivering email to the local machine may take too long when you have to
20
20
  send hundreds of messages. ar_mailer allows you to store messages into the
21
21
  database for later delivery by a separate process, ar_sendmail.
22
22
 
@@ -30,6 +30,9 @@ See ActionMailer::ARMailer for instructions on converting to ARMailer.
30
30
 
31
31
  See ar_sendmail -h for options to ar_sendmail.
32
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
+
33
36
  === ar_sendmail on FreeBSD or NetBSD
34
37
 
35
38
  An rc.d script is included in share/ar_sendmail.
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'hoe'
2
2
 
3
- Hoe.new 'ar_mailer', '1.2.0' do |s|
3
+ require './lib/action_mailer/ar_sendmail'
4
+
5
+ Hoe.new 'ar_mailer', ActionMailer::ARSendmail::VERSION do |s|
4
6
  s.rubyforge_name = 'seattlerb'
5
7
  s.summary = s.paragraphs_of('README.txt', 1).join(' ')
6
8
  s.description = s.paragraphs_of('README.txt', 9).join(' ')
@@ -67,8 +67,6 @@ require 'action_mailer'
67
67
 
68
68
  class ActionMailer::ARMailer < ActionMailer::Base
69
69
 
70
- VERSION = '1.2.0' # :nodoc:
71
-
72
70
  @@email_class = Email
73
71
 
74
72
  ##
@@ -51,6 +51,16 @@ module ActionMailer; end # :nodoc:
51
51
 
52
52
  class ActionMailer::ARSendmail
53
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
+
54
64
  ##
55
65
  # Email delivery attempts per run
56
66
 
@@ -61,6 +71,11 @@ class ActionMailer::ARSendmail
61
71
 
62
72
  attr_accessor :delay
63
73
 
74
+ ##
75
+ # Maximum age of emails in seconds before they are removed from the queue.
76
+
77
+ attr_accessor :max_age
78
+
64
79
  ##
65
80
  # Be verbose
66
81
 
@@ -76,6 +91,11 @@ class ActionMailer::ARSendmail
76
91
 
77
92
  attr_reader :once
78
93
 
94
+ ##
95
+ # Times authentication has failed
96
+
97
+ attr_accessor :failed_auth_count
98
+
79
99
  ##
80
100
  # Creates a new migration using +table_name+ and prints it on stdout.
81
101
 
@@ -94,7 +114,7 @@ class Add#{table_name.classify} < ActiveRecord::Migration
94
114
  end
95
115
 
96
116
  def self.down
97
- drop_table :emails
117
+ drop_table :#{table_name.tableize}
98
118
  end
99
119
  end
100
120
  EOF
@@ -134,8 +154,8 @@ end
134
154
  size = email.mail.length
135
155
  total_size += size
136
156
 
137
- create_timestamp = email.created_at rescue
138
- email.created_on rescue
157
+ create_timestamp = email.created_on rescue
158
+ email.created_at rescue
139
159
  Time.at(email.created_date) rescue # for Robot Co-op
140
160
  nil
141
161
 
@@ -163,12 +183,13 @@ end
163
183
  name = File.basename $0
164
184
 
165
185
  options = {}
186
+ options[:Chdir] = '.'
166
187
  options[:Daemon] = false
167
188
  options[:Delay] = 60
189
+ options[:MaxAge] = 86400 * 7
168
190
  options[:Once] = false
169
- options[:TableName] = 'Email'
170
- options[:Chdir] = '.'
171
191
  options[:RailsEnv] = ENV['RAILS_ENV']
192
+ options[:TableName] = 'Email'
172
193
 
173
194
  opts = OptionParser.new do |opts|
174
195
  opts.banner = "Usage: #{name} [options]"
@@ -195,6 +216,14 @@ end
195
216
  options[:Delay] = delay
196
217
  end
197
218
 
219
+ opts.on( "--max-age MAX_AGE",
220
+ "Maxmimum age for an email. After this",
221
+ "it will be removed from the queue.",
222
+ "Set to 0 to disable queue cleanup.",
223
+ "Default: #{options[:MaxAge]} seconds", Integer) do |max_age|
224
+ options[:MaxAge] = max_age
225
+ end
226
+
198
227
  opts.on("-o", "--once",
199
228
  "Only check for new mail and deliver once",
200
229
  "Default: #{options[:Once]}") do |once|
@@ -346,24 +375,42 @@ end
346
375
  def initialize(options = {})
347
376
  options[:Delay] ||= 60
348
377
  options[:TableName] ||= 'Email'
378
+ options[:MaxAge] ||= 86400 * 7
349
379
 
350
380
  @batch_size = options[:BatchSize]
351
381
  @delay = options[:Delay]
352
382
  @email_class = Object.path2class options[:TableName]
353
383
  @once = options[:Once]
354
384
  @verbose = options[:Verbose]
385
+ @max_age = options[:MaxAge]
386
+
387
+ @failed_auth_count = 0
388
+ end
389
+
390
+ ##
391
+ # Removes emails that have lived in the queue for too long. If max_age is
392
+ # set to 0, no emails will be removed.
393
+
394
+ def cleanup
395
+ return if @max_age == 0
396
+ timeout = Time.now - @max_age
397
+ conditions = ['last_send_attempt > 0 and created_on < ?', timeout]
398
+ mail = @email_class.destroy_all conditions
399
+
400
+ log "expired #{mail.length} emails from the queue"
355
401
  end
356
402
 
357
403
  ##
358
404
  # Delivers +emails+ to ActionMailer's SMTP server and destroys them.
359
405
 
360
406
  def deliver(emails)
361
- user = server_settings[:user] || server_settings[:user_name]
362
- Net::SMTP.start server_settings[:address], server_settings[:port],
363
- server_settings[:domain], user,
364
- server_settings[:password],
365
- server_settings[:authentication],
366
- server_settings[:tls] do |smtp|
407
+ user = smtp_settings[:user] || smtp_settings[:user_name]
408
+ Net::SMTP.start smtp_settings[:address], smtp_settings[:port],
409
+ smtp_settings[:domain], user,
410
+ smtp_settings[:password],
411
+ smtp_settings[:authentication],
412
+ smtp_settings[:tls] do |smtp|
413
+ @failed_auth_count = 0
367
414
  until emails.empty? do
368
415
  email = emails.shift
369
416
  begin
@@ -378,9 +425,9 @@ end
378
425
  smtp.reset
379
426
  rescue Net::SMTPServerBusy => e
380
427
  log "server too busy, sleeping #{@delay} seconds"
381
- sleep delay unless $TESTING
428
+ sleep delay
382
429
  return
383
- rescue Net::SMTPServerBusy, Net::SMTPUnknownError, Net::SMTPSyntaxError, TimeoutError => e
430
+ rescue Net::SMTPUnknownError, Net::SMTPSyntaxError, TimeoutError => e
384
431
  email.last_send_attempt = Time.now.to_i
385
432
  email.save rescue nil
386
433
  log "error sending email %d: %p(%s):\n\t%s" %
@@ -389,7 +436,16 @@ end
389
436
  end
390
437
  end
391
438
  end
392
- rescue Net::SMTPServerBusy, SystemCallError
439
+ rescue Net::SMTPAuthenticationError => e
440
+ @failed_auth_count += 1
441
+ if @failed_auth_count >= MAX_AUTH_FAILURES then
442
+ log "authentication error, giving up: #{e.message}"
443
+ raise e
444
+ else
445
+ log "authentication error, retrying: #{e.message}"
446
+ end
447
+ sleep delay
448
+ rescue Net::SMTPServerBusy, SystemCallError, OpenSSL::SSL::SSLError
393
449
  # ignore SMTPServerBusy/EPIPE/ECONNRESET from Net::SMTP.start's ensure
394
450
  end
395
451
 
@@ -440,6 +496,7 @@ end
440
496
  loop do
441
497
  now = Time.now
442
498
  begin
499
+ cleanup
443
500
  deliver find_emails
444
501
  rescue ActiveRecord::Transactions::TransactionError
445
502
  end
@@ -449,12 +506,15 @@ end
449
506
  end
450
507
 
451
508
  ##
452
- # Proxy to ActionMailer::Base#server_settings. See
509
+ # Proxy to ActionMailer::Base::smtp_settings. See
453
510
  # http://api.rubyonrails.org/classes/ActionMailer/Base.html
454
511
  # for instructions on how to configure ActionMailer's SMTP server.
512
+ #
513
+ # Falls back to ::server_settings if ::smtp_settings doesn't exist for
514
+ # backwards compatibility.
455
515
 
456
- def server_settings
457
- ActionMailer::Base.server_settings
516
+ def smtp_settings
517
+ ActionMailer::Base.smtp_settings rescue ActionMailer::Base.server_settings
458
518
  end
459
519
 
460
520
  end
@@ -10,6 +10,8 @@ class Net::SMTP
10
10
 
11
11
  @send_message_block = nil
12
12
 
13
+ @start_block = nil
14
+
13
15
  class << self
14
16
 
15
17
  attr_reader :deliveries
@@ -21,6 +23,7 @@ class Net::SMTP
21
23
  end
22
24
 
23
25
  def self.start(*args)
26
+ @start_block.call if @start_block
24
27
  yield new(nil)
25
28
  end
26
29
 
@@ -28,8 +31,13 @@ class Net::SMTP
28
31
  @send_message_block = block
29
32
  end
30
33
 
34
+ def self.on_start(&block)
35
+ @start_block = block
36
+ end
37
+
31
38
  def self.reset
32
39
  deliveries.clear
40
+ on_start
33
41
  on_send_message
34
42
  @reset_called = 0
35
43
  end
@@ -105,11 +113,27 @@ class Email
105
113
  class << self; attr_accessor :records, :id; end
106
114
 
107
115
  def self.create(record)
108
- record = new record[:from], record[:to], record[:mail]
116
+ record = new record[:from], record[:to], record[:mail],
117
+ record[:last_send_attempt]
109
118
  records << record
110
119
  return record
111
120
  end
112
121
 
122
+ def self.destroy_all(conditions)
123
+ timeout = conditions.last
124
+ found = []
125
+
126
+ records.each do |record|
127
+ next if record.last_send_attempt == 0
128
+ next if record.created_on == 0
129
+ next unless record.created_on < timeout
130
+ record.destroy
131
+ found << record
132
+ end
133
+
134
+ found
135
+ end
136
+
113
137
  def self.find(_, conditions = nil)
114
138
  return records if conditions.nil?
115
139
  now = Time.now.to_i - 300
@@ -123,13 +147,13 @@ class Email
123
147
  records.clear
124
148
  end
125
149
 
126
- def initialize(from, to, mail)
150
+ def initialize(from, to, mail, last_send_attempt = nil)
127
151
  @from = from
128
152
  @to = to
129
153
  @mail = mail
130
154
  @id = self.class.id += 1
131
155
  @created_on = START + @id
132
- @last_send_attempt = 0
156
+ @last_send_attempt = last_send_attempt || 0
133
157
  end
134
158
 
135
159
  def destroy
@@ -4,7 +4,13 @@ require 'action_mailer/ar_sendmail'
4
4
  require 'rubygems'
5
5
  require 'test/zentest_assertions'
6
6
 
7
- $TESTING = true
7
+ class ActionMailer::ARSendmail
8
+ attr_accessor :slept
9
+ def sleep(secs)
10
+ @slept ||= []
11
+ @slept << secs
12
+ end
13
+ end
8
14
 
9
15
  class TestARSendmail < Test::Unit::TestCase
10
16
 
@@ -42,7 +48,7 @@ class AddMail < ActiveRecord::Migration
42
48
  end
43
49
 
44
50
  def self.down
45
- drop_table :emails
51
+ drop_table :mail
46
52
  end
47
53
  end
48
54
  EOF
@@ -206,6 +212,17 @@ Last send attempt: Thu Aug 10 11:40:05 -0700 2006
206
212
  assert_equal true, options[:MailQ]
207
213
  end
208
214
 
215
+ def test_class_parse_args_max_age
216
+ options = ActionMailer::ARSendmail.process_args []
217
+ assert_equal 86400 * 7, options[:MaxAge]
218
+
219
+ argv = %w[--max-age 86400]
220
+
221
+ options = ActionMailer::ARSendmail.process_args argv
222
+
223
+ assert_equal 86400, options[:MaxAge]
224
+ end
225
+
209
226
  def test_class_parse_args_migration
210
227
  options = ActionMailer::ARSendmail.process_args []
211
228
  deny_includes :Migration, options
@@ -318,6 +335,39 @@ Last send attempt: Thu Aug 10 11:40:05 -0700 2006
318
335
  assert_equal "hi\n\nopts\n", err.string
319
336
  end
320
337
 
338
+ def test_cleanup
339
+ e1 = Email.create :mail => 'body', :to => 'to', :from => 'from'
340
+ e1.created_on = Time.now
341
+ e2 = Email.create :mail => 'body', :to => 'to', :from => 'from'
342
+ e3 = Email.create :mail => 'body', :to => 'to', :from => 'from'
343
+ e3.last_send_attempt = Time.now
344
+
345
+ out, err = util_capture do
346
+ @sm.cleanup
347
+ end
348
+
349
+ assert_equal '', out.string
350
+ assert_equal "expired 1 emails from the queue\n", err.string
351
+ assert_equal 2, Email.records.length
352
+
353
+ assert_equal [e1, e2], Email.records
354
+ end
355
+
356
+ def test_cleanup_disabled
357
+ e1 = Email.create :mail => 'body', :to => 'to', :from => 'from'
358
+ e1.created_on = Time.now
359
+ e2 = Email.create :mail => 'body', :to => 'to', :from => 'from'
360
+
361
+ @sm.max_age = 0
362
+
363
+ out, err = util_capture do
364
+ @sm.cleanup
365
+ end
366
+
367
+ assert_equal '', out.string
368
+ assert_equal 2, Email.records.length
369
+ end
370
+
321
371
  def test_deliver
322
372
  email = Email.create :mail => 'body', :to => 'to', :from => 'from'
323
373
 
@@ -334,6 +384,61 @@ Last send attempt: Thu Aug 10 11:40:05 -0700 2006
334
384
  assert_equal "sent email 00000000001 from from to to: \"queued\"\n", err.string
335
385
  end
336
386
 
387
+ def test_deliver_auth_error
388
+ Net::SMTP.on_start do
389
+ e = Net::SMTPAuthenticationError.new 'try again'
390
+ e.set_backtrace %w[one two three]
391
+ raise e
392
+ end
393
+
394
+ now = Time.now.to_i
395
+
396
+ email = Email.create :mail => 'body', :to => 'to', :from => 'from'
397
+
398
+ out, err = util_capture do
399
+ @sm.deliver [email]
400
+ end
401
+
402
+ assert_equal 0, Net::SMTP.deliveries.length
403
+ assert_equal 1, Email.records.length
404
+ assert_equal 0, Email.records.first.last_send_attempt
405
+ assert_equal 0, Net::SMTP.reset_called
406
+ assert_equal 1, @sm.failed_auth_count
407
+ assert_equal [60], @sm.slept
408
+
409
+ assert_equal '', out.string
410
+ assert_equal "authentication error, retrying: try again\n", err.string
411
+ end
412
+
413
+ def test_deliver_auth_error_recover
414
+ email = Email.create :mail => 'body', :to => 'to', :from => 'from'
415
+ @sm.failed_auth_count = 1
416
+
417
+ out, err = util_capture do @sm.deliver [email] end
418
+
419
+ assert_equal 0, @sm.failed_auth_count
420
+ assert_equal 1, Net::SMTP.deliveries.length
421
+ end
422
+
423
+ def test_deliver_auth_error_twice
424
+ Net::SMTP.on_start do
425
+ e = Net::SMTPAuthenticationError.new 'try again'
426
+ e.set_backtrace %w[one two three]
427
+ raise e
428
+ end
429
+
430
+ @sm.failed_auth_count = 1
431
+
432
+ out, err = util_capture do
433
+ assert_raise Net::SMTPAuthenticationError do
434
+ @sm.deliver []
435
+ end
436
+ end
437
+
438
+ assert_equal 2, @sm.failed_auth_count
439
+ assert_equal "authentication error, giving up: try again\n", err.string
440
+ end
441
+
337
442
  def test_deliver_4xx_error
338
443
  Net::SMTP.on_send_message do
339
444
  e = Net::SMTPSyntaxError.new 'try again'
@@ -422,6 +527,7 @@ Last send attempt: Thu Aug 10 11:40:05 -0700 2006
422
527
  assert_equal 1, Email.records.length
423
528
  assert_operator now, :>=, Email.records.first.last_send_attempt
424
529
  assert_equal 0, Net::SMTP.reset_called, 'Reset connection on SyntaxError'
530
+ assert_equal [60], @sm.slept
425
531
 
426
532
  assert_equal '', out.string
427
533
  assert_equal "server too busy, sleeping 60 seconds\n", err.string
@@ -522,10 +628,10 @@ Last send attempt: Thu Aug 10 11:40:05 -0700 2006
522
628
  assert_equal "found 3 emails to send\n", err.string
523
629
  end
524
630
 
525
- def test_server_settings
631
+ def test_smtp_settings
526
632
  ActionMailer::Base.server_settings[:address] = 'localhost'
527
633
 
528
- assert_equal 'localhost', @sm.server_settings[:address]
634
+ assert_equal 'localhost', @sm.smtp_settings[:address]
529
635
  end
530
636
 
531
637
  def nobody
metadata CHANGED
@@ -1,22 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.4
2
+ rubygems_version: 0.9.4.3
3
3
  specification_version: 1
4
4
  name: ar_mailer
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.2.0
7
- date: 2007-06-05 00:00:00 -07:00
6
+ version: 1.3.1
7
+ date: 2007-07-31 00:00:00 -07:00
8
8
  summary: A two-phase delivery agent for ActionMailer
9
9
  require_paths:
10
10
  - lib
11
11
  email: drbrain@segment7.net
12
12
  homepage: http://seattlerb.org/ar_mailer
13
13
  rubyforge_project: seattlerb
14
- description: Even deliviring email to the local machine may take too long when you have to send hundreds of messages. ar_mailer allows you to store messages into the database for later delivery by a separate process, ar_sendmail.
14
+ description: Even delivering email to the local machine may take too long when you have to send hundreds of messages. ar_mailer allows you to store messages into the database for later delivery by a separate process, ar_sendmail.
15
15
  autorequire:
16
16
  default_executable:
17
17
  bindir: bin
18
18
  has_rdoc: true
19
- required_ruby_version: !ruby/object:Gem::Version::Requirement
19
+ required_ruby_version: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ required_rubygems_version: !ruby/object:Gem::Requirement
20
26
  requirements:
21
27
  - - ">"
22
28
  - !ruby/object:Gem::Version
@@ -63,9 +69,9 @@ dependencies:
63
69
  - !ruby/object:Gem::Dependency
64
70
  name: hoe
65
71
  version_requirement:
66
- version_requirements: !ruby/object:Gem::Version::Requirement
72
+ version_requirements: !ruby/object:Gem::Requirement
67
73
  requirements:
68
74
  - - ">="
69
75
  - !ruby/object:Gem::Version
70
- version: 1.2.1
76
+ version: 1.2.2
71
77
  version: