ar_mailer 1.2.0 → 1.3.1

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 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: