VvanGemert-ar_mailer 2.1.8 → 2.2.0

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.
@@ -16,4 +16,9 @@ http://github.com/adzap/ar_mailer/wikis
16
16
 
17
17
  == Changes to the Adzap_ar_mailer version
18
18
 
19
- - Added priority
19
+ - Added priority, the higher the priority the faster it will send
20
+ - Added newsletter support, loops through user table and is connected with the batch size
21
+ - Added bounce check support, updates user with bounced_at field when bounced
22
+ - Added dry run support, dry run doesn't send emails and doesn't check for bounced
23
+
24
+ Use "ar_sendmail -h" for all the options
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'rubygems'
2
2
  require 'rake/gempackagetask'
3
- require 'rake/testtask'
4
3
  require 'rake/rdoctask'
5
4
 
6
5
  $:.unshift(File.expand_path(File.dirname(__FILE__) + '/lib'))
@@ -44,9 +43,7 @@ ar_mailer_gemspec = Gem::Specification.new do |s|
44
43
  s.require_paths = ["lib"]
45
44
  s.rubyforge_project = %q{seattlerb}
46
45
  s.summary = %q{A two-phase delivery agent for ActionMailer}
47
- s.test_files = ["test/test_armailer.rb", "test/test_arsendmail.rb"]
48
- s.add_development_dependency "minitest", ">= 1.5.0"
49
- s.add_development_dependency "mocha", ">= 0.9.8"
46
+ s.test_files = []
50
47
  end
51
48
 
52
49
  Rake::GemPackageTask.new(ar_mailer_gemspec) do |pkg|
@@ -64,13 +61,3 @@ desc "Build packages and install"
64
61
  task :install => :package do
65
62
  sh %{sudo gem install --local --test pkg/VvanGemert-ar_mailer-#{ActionMailer::ARSendmail::VERSION}}
66
63
  end
67
-
68
- desc 'Default: run unit tests.'
69
- task :default => :test
70
-
71
- desc 'Test the ar_mailer gem.'
72
- Rake::TestTask.new(:test) do |t|
73
- t.libs << 'lib' << 'test'
74
- t.test_files = FileList['test/**/test_*.rb'].exclude("test/test_helper.rb")
75
- t.verbose = true
76
- end
@@ -9,6 +9,8 @@ class ActionMailer::Base
9
9
  # Set the email class for deliveries. Handle class reloading issues which prevents caching the email class.
10
10
  #
11
11
  @@email_class_name = 'Email'
12
+ @@newsletter_class_name = 'EmailNewsletter'
13
+ @@user_class_name = 'User'
12
14
  @@priority = 100
13
15
 
14
16
  def self.email_class=(klass)
@@ -19,6 +21,22 @@ class ActionMailer::Base
19
21
  @@email_class_name.constantize
20
22
  end
21
23
 
24
+ def self.newsletter_class=(klass)
25
+ @@newsletter_class_name = klass.to_s
26
+ end
27
+
28
+ def self.newsletter_class
29
+ @@newsletter_class_name.constantize
30
+ end
31
+
32
+ def self.user_class=(klass)
33
+ @@user_class_name = klass.to_s
34
+ end
35
+
36
+ def self.user_class
37
+ @@user_class_name.constantize
38
+ end
39
+
22
40
  ##
23
41
  # Adds +mail+ to the Email table. Only the first From address for +mail+ is
24
42
  # used.
@@ -1,5 +1,6 @@
1
1
  require 'optparse'
2
2
  require 'net/smtp'
3
+ require 'net/imap'
3
4
  require 'smtp_tls' unless Net::SMTP.instance_methods.include?("enable_starttls_auto")
4
5
 
5
6
  ##
@@ -146,6 +147,11 @@ class ActionMailer::ARSendmail
146
147
  options[:MaxAge] = 86400 * 7
147
148
  options[:Once] = false
148
149
  options[:RailsEnv] = ENV['RAILS_ENV']
150
+ options[:Port] = 993
151
+ options[:Login] = ''
152
+ options[:Imap] = ''
153
+ options[:Password] = ''
154
+ options[:DryRun] = false
149
155
  options[:Pidfile] = options[:Chdir] + '/log/ar_sendmail.pid'
150
156
 
151
157
  opts = OptionParser.new do |opts|
@@ -229,7 +235,43 @@ class ActionMailer::ARSendmail
229
235
  "Default: #{options[:Verbose]}") do |verbose|
230
236
  options[:Verbose] = verbose
231
237
  end
232
-
238
+
239
+ opts.on("-i", "--imap IMAP",
240
+ "Imap server used to check for bounces",
241
+ "Default: false", String) do |imap|
242
+ options[:Imap] = imap
243
+ end
244
+
245
+ opts.on("-l", "--login LOGIN",
246
+ "login name to check for bounces",
247
+ "Default: false", String) do |login|
248
+ options[:Login] = login
249
+ end
250
+
251
+ opts.on( "--password PASSWORD",
252
+ "password name to check for bounces",
253
+ "Default: false", String) do |password|
254
+ options[:Password] = password
255
+ end
256
+
257
+ opts.on( "--port PORT",
258
+ "port to check for bounces",
259
+ "Default: #{options[:Port]}", Integer) do |port|
260
+ options[:Port] = port
261
+ end
262
+
263
+ opts.on( "-k", "--bouncecheck",
264
+ "check for bounces",
265
+ "Default: false") do |bounce_check|
266
+ options[:Bouncecheck] = true
267
+ end
268
+
269
+ opts.on("-f", "--dry-run",
270
+ "Dry run: don't send any emails",
271
+ "Default: Deliver all available emails\n", options[:DryRun]) do |dry_run|
272
+ options[:DryRun] = dry_run
273
+ end
274
+
233
275
  opts.on("-h", "--help",
234
276
  "You're looking at it") do
235
277
  usage opts
@@ -335,7 +377,9 @@ class ActionMailer::ARSendmail
335
377
  @once = options[:Once]
336
378
  @verbose = options[:Verbose]
337
379
  @max_age = options[:MaxAge]
338
-
380
+ @dry_run = options[:DryRun]
381
+ @imap = { :host => options[:Imap], :port => options[:Port], :user => options[:Login], :password => options[:Password] }
382
+ @bouncecheck = options[:Bouncecheck]
339
383
  @failed_auth_count = 0
340
384
  end
341
385
 
@@ -375,8 +419,12 @@ class ActionMailer::ARSendmail
375
419
  until emails.empty? do
376
420
  email = emails.shift
377
421
  begin
378
- res = session.send_message email.mail, email.from, email.to
379
- email.destroy
422
+ if @dry_run
423
+ res = 'DRY RUN'
424
+ else
425
+ res = session.send_message email.mail, email.from, email.to
426
+ email.destroy
427
+ end
380
428
  log "sent email %011d from %s to %s: %p" %
381
429
  [email.id, email.from, email.to, res]
382
430
  rescue Net::SMTPFatalError => e
@@ -389,7 +437,7 @@ class ActionMailer::ARSendmail
389
437
  return
390
438
  rescue Net::SMTPUnknownError, Net::SMTPSyntaxError, TimeoutError, Timeout::Error => e
391
439
  email.last_send_attempt = Time.now.to_i
392
- email.save rescue nil
440
+ email.save rescue nil unless @dry_run
393
441
  log "error sending email %d: %p(%s):\n\t%s" %
394
442
  [email.id, e.message, e.class, e.backtrace.join("\n\t")]
395
443
  session.reset
@@ -409,6 +457,61 @@ class ActionMailer::ARSendmail
409
457
  # ignore SMTPServerBusy/EPIPE/ECONNRESET from Net::SMTP.start's ensure
410
458
  end
411
459
 
460
+ def deliver_newsletter(newsletter, users)
461
+
462
+ settings = [
463
+ smtp_settings[:domain],
464
+ (smtp_settings[:user] || smtp_settings[:user_name]),
465
+ smtp_settings[:password],
466
+ smtp_settings[:authentication]
467
+ ]
468
+
469
+ smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port])
470
+ if smtp.respond_to?(:enable_starttls_auto)
471
+ smtp.enable_starttls_auto unless smtp_settings[:tls] == false
472
+ else
473
+ settings << smtp_settings[:tls]
474
+ end
475
+
476
+ smtp.start(*settings) do |session|
477
+ @failed_auth_count = 0
478
+ until users.empty? do
479
+ user = users.shift
480
+ begin
481
+ if @dry_run
482
+ res = 'DRY RUN'
483
+ else
484
+ res = session.send_message newsletter.mail, newsletter.from, user.email
485
+ end
486
+ log "sent email %011d from %s to %s: %p" %
487
+ [newsletter.id, newsletter.from, user.email, res]
488
+ rescue Net::SMTPFatalError => e
489
+ log "5xx error sending email %d, removing from queue: %p(%s):\n\t%s" %
490
+ [email.id, e.message, e.class, e.backtrace.join("\n\t")]
491
+ session.reset
492
+ rescue Net::SMTPServerBusy => e
493
+ log "server too busy, stopping delivery cycle"
494
+ return
495
+ rescue Net::SMTPUnknownError, Net::SMTPSyntaxError, TimeoutError, Timeout::Error => e
496
+ log "error sending email %d: %p(%s):\n\t%s" %
497
+ [email.id, e.message, e.class, e.backtrace.join("\n\t")]
498
+ session.reset
499
+ end
500
+ end
501
+ end
502
+ rescue Net::SMTPAuthenticationError => e
503
+ @failed_auth_count += 1
504
+ if @failed_auth_count >= MAX_AUTH_FAILURES then
505
+ log "authentication error, giving up: #{e.message}"
506
+ raise e
507
+ else
508
+ log "authentication error, retrying: #{e.message}"
509
+ end
510
+ sleep delay
511
+ rescue Net::SMTPServerBusy, SystemCallError, OpenSSL::SSL::SSLError
512
+ # ignore SMTPServerBusy/EPIPE/ECONNRESET from Net::SMTP.start's ensure
513
+ end
514
+
412
515
  ##
413
516
  # Prepares ar_sendmail for exiting
414
517
 
@@ -430,7 +533,31 @@ class ActionMailer::ARSendmail
430
533
  log "found #{mail.length} emails to send"
431
534
  mail
432
535
  end
433
-
536
+
537
+ def find_newsletter
538
+
539
+ options = { :conditions => ['cancelled != 1 AND completed != 1'] }
540
+ newsletter = ActionMailer::Base.newsletter_class.find :first, options
541
+
542
+ log "found newsletter with title: #{newsletter.title}" unless newsletter.nil?
543
+ log "no newsletters found" unless !newsletter.nil?
544
+ newsletter
545
+
546
+ end
547
+
548
+ def find_users(limit, offset)
549
+
550
+ options = { :conditions => ['newsletter = 1 AND email IS NOT NULL'], :limit => limit, :offset => offset, :order => "id" }
551
+ users = ActionMailer::Base.user_class.find :all, options
552
+
553
+ if !users.nil? && users.length < limit
554
+
555
+ end
556
+ log "found #{users.length} users to send a newsletter"
557
+ users
558
+
559
+ end
560
+
434
561
  ##
435
562
  # Installs signal handlers to gracefully exit.
436
563
 
@@ -457,8 +584,35 @@ class ActionMailer::ARSendmail
457
584
  loop do
458
585
  begin
459
586
  cleanup
587
+
460
588
  emails = find_emails
589
+ already_send = emails.empty? ? 0 : emails.length
461
590
  deliver(emails) unless emails.empty?
591
+
592
+ newsletter = find_newsletter
593
+
594
+ unless newsletter.nil? || batch_size.nil?
595
+ offset = newsletter.mails_send.nil? ? 0 : newsletter.mails_send
596
+ limit = batch_size - already_send
597
+
598
+ users = find_users(limit, offset)
599
+
600
+ update = {}
601
+ update[:mails_send] = offset + users.length unless users.empty?
602
+
603
+ if (!users.empty? && users.length < limit) || users.empty?
604
+ update[:completed] = 1
605
+ end
606
+
607
+ # Deliver newsletter
608
+ newsletter.update_attributes(update) unless @dry_run
609
+ deliver_newsletter(newsletter, users) unless users.empty?
610
+
611
+ end
612
+
613
+ # Check for bounces
614
+ check_bounces unless @dry_run
615
+
462
616
  rescue ActiveRecord::Transactions::TransactionError
463
617
  end
464
618
  break if @once
@@ -478,4 +632,92 @@ class ActionMailer::ARSendmail
478
632
  ActionMailer::Base.smtp_settings rescue ActionMailer::Base.server_settings
479
633
  end
480
634
 
635
+ def check_bounces
636
+
637
+ if @bouncecheck
638
+ begin
639
+ imap = Net::IMAP.new(@imap[:host], @imap[:port], true)
640
+ imap.login(@imap[:user], @imap[:password])
641
+ imap.select('INBOX')
642
+
643
+ imap.uid_search(["NOT", "DELETED"]).each do |message_id|
644
+ msg = imap.uid_fetch(message_id,'RFC822')[0].attr['RFC822']
645
+ email = TMail::Mail.parse(msg)
646
+ receive(email)
647
+ #Mark message as deleted and it will be removed from storage when user session closed
648
+ imap.uid_store(message_id, "+FLAGS", [:Deleted])
649
+ end
650
+ # tell server to permanently remove all messages flagged as :Deleted
651
+ imap.expunge
652
+ imap.logout
653
+ imap.disconnect
654
+ rescue Net::IMAP::NoResponseError => e
655
+ log e
656
+ rescue Net::IMAP::ByeResponseError => e
657
+ log e
658
+ rescue => e
659
+ log e
660
+ end
661
+ end
662
+
663
+ end
664
+
665
+ def receive(email)
666
+ bounce = BouncedDelivery.from_email(email)
667
+ if(bounce.status_info > 3)
668
+ log "User bounced with email: #{bounce.sender}"
669
+ user = ActionMailer::Base.user_class.find_by_email(bounce.sender)
670
+
671
+ if !user.nil?
672
+ user.update_attribute(:bounced_at, Time.now)
673
+ end
674
+ end
675
+ end
676
+
481
677
  end
678
+
679
+ ##
680
+ # Checking for bounced delivery
681
+ #
682
+
683
+ class BouncedDelivery
684
+
685
+ attr_accessor :status_info, :sender, :subject
686
+
687
+ def self.from_email(email)
688
+ returning(bounce = self.new) do
689
+
690
+ if(email.subject.match(/Mail delivery failed/i))
691
+ bounce.status_info = 6
692
+ elsif(email.subject.match(/Delivery Status Notification/i))
693
+ bounce.status_info = 8
694
+ elsif(email.header.to_s.match(/X-Failed-Recipient/i))
695
+ bounce.status_info = 10
696
+ else
697
+ bounce.status_info = 2
698
+ end
699
+
700
+ if bounce.status_info > 3
701
+ bounce.subject = email.subject
702
+ bounce.sender = bounce.check_recipient(email.header)
703
+ end
704
+ end
705
+ end
706
+
707
+ def check_recipient(header)
708
+ header['x-failed-recipients'].body.to_s unless header['x-failed-recipients'].nil?
709
+ end
710
+
711
+ def status
712
+ case status_info
713
+ when 10
714
+ 'Failed - X-Failed-Recipient'
715
+ when 8
716
+ 'Failed - Delivery Status Notification'
717
+ when 6
718
+ 'Failed - Mail delivery failed'
719
+ when 2
720
+ 'Success'
721
+ end
722
+ end
723
+ end
@@ -1 +1,2 @@
1
1
  require 'action_mailer/ar_mailer'
2
+ require 'action_mailer/ar_newsletter'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: VvanGemert-ar_mailer
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 7
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
- - 1
9
- - 8
10
- version: 2.1.8
8
+ - 2
9
+ - 0
10
+ version: 2.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Eric Hodel
@@ -18,39 +18,8 @@ cert_chain: []
18
18
 
19
19
  date: 2010-03-17 00:00:00 +01:00
20
20
  default_executable: ar_sendmail
21
- dependencies:
22
- - !ruby/object:Gem::Dependency
23
- name: minitest
24
- prerelease: false
25
- requirement: &id001 !ruby/object:Gem::Requirement
26
- none: false
27
- requirements:
28
- - - ">="
29
- - !ruby/object:Gem::Version
30
- hash: 3
31
- segments:
32
- - 1
33
- - 5
34
- - 0
35
- version: 1.5.0
36
- type: :development
37
- version_requirements: *id001
38
- - !ruby/object:Gem::Dependency
39
- name: mocha
40
- prerelease: false
41
- requirement: &id002 !ruby/object:Gem::Requirement
42
- none: false
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- hash: 43
47
- segments:
48
- - 0
49
- - 9
50
- - 8
51
- version: 0.9.8
52
- type: :development
53
- version_requirements: *id002
21
+ dependencies: []
22
+
54
23
  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.
55
24
  email: vincent@floorplanner.com
56
25
  executables: