smailer 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/smailer.rb CHANGED
@@ -1,5 +1,7 @@
1
+ require 'smailer/constants'
2
+ require 'smailer/version'
3
+
1
4
  module Smailer
2
- autoload :VERSION, 'smailer/version'
3
5
  autoload :Models, 'smailer/models'
4
6
  autoload :Tasks, 'smailer/tasks'
5
7
  autoload :Compatibility, 'smailer/compatibility'
@@ -0,0 +1,3 @@
1
+ module Smailer
2
+ BOUNCES_PREFIX = 'bounces-'.freeze
3
+ end
data/lib/smailer/tasks.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module Smailer
2
2
  module Tasks
3
3
  autoload :Send, 'smailer/tasks/send'
4
+ autoload :ProcessBounces, 'smailer/tasks/process_bounces'
4
5
  end
5
6
  end
@@ -0,0 +1,162 @@
1
+ require 'bounce_email'
2
+ require 'net/pop'
3
+
4
+ module Smailer
5
+ module Tasks
6
+ class ProcessBounces
7
+ @@keys_messages = {}
8
+ @@bounce_counts = Hash.new(0)
9
+ @@emails_bounces = {}
10
+ @@unsubscribed = {}
11
+ @@test_mode = false
12
+
13
+ class << self
14
+ # You need to provide at least a :server, :username and a :password options to execute().
15
+ # These will represent the POP3 connection details to the bounce mailbox which will be processed.
16
+ # Also consider providing a concrete implementation to the :subscribed_checker option, so that
17
+ # bounces for unknown or already-unsubscribed emails do not remain clogging-up the mailbox
18
+ # Example usage:
19
+ #
20
+ # Smailer::Tasks::ProcessBounces.execute({
21
+ # :server => 'bounces.example.org',
22
+ # :username => 'noreply@bounces.example.org',
23
+ # :password => 'somesecret',
24
+ # :subscribed_checker => lambda { |recipient| Subscribers.subscribed.where(:email => recipient).first.present? },
25
+ # }) do |unsubscribe_details|
26
+ # Subscribers.where(:email => unsubscribe_details[:recipient]).destroy
27
+ # end
28
+ def execute(options = {})
29
+ deleted = 0
30
+ options = {
31
+ :port => 110,
32
+ :subscribed_checker => lambda { true },
33
+ :bounce_counts_per_type_to_unsubscribe => {
34
+ 1 => ['5.1.1', '5.1.2', '5.1.3', '5.1.6', '5.1.8', '5.7.1'],
35
+ 3 => ['5.2.1', '5.1.0'],
36
+ 5 => ['5.5.0'],
37
+ 8 => ['5.0.0'],
38
+ 12 => ['5.2.2', '5.2.0', '5.3.0', '5.3.2'],
39
+ 16 => ['4.2.2', '4.4.2', '4.0.0'],
40
+ 30 => ['97', '98', '99'],
41
+ },
42
+ :default_bounce_count_per_type_to_unsubscribe => 20,
43
+ :total_bounce_count_to_unsubscribe => 40,
44
+ :delete_unprocessed_bounces => true,
45
+ :logger => lambda { |msg| puts msg },
46
+ }.merge(options)
47
+
48
+ logger = lambda do |msg|
49
+ options[:logger].call msg if options[:logger] && options[:logger].respond_to?(:call)
50
+ end
51
+
52
+ connect_to_mailbox(options) do |pop|
53
+ mails_to_process = pop.mails.size
54
+ logger.call "#{mails_to_process} mail(s) to process"
55
+
56
+ pop.mails.each_with_index do |m, i|
57
+ logger.call "#{((i + 1) * 100.0 / mails_to_process).round(2)}% (#{i + 1} of #{mails_to_process})" if i % 200 == 0
58
+
59
+ mail = Mail.new m.pop
60
+ bounce = BounceEmail::Mail.new mail
61
+ processed = false
62
+
63
+ if bounce.bounced?
64
+ original_email = sent_message_from_bounce(mail)
65
+
66
+ if original_email
67
+ recipient = original_email.to
68
+
69
+ if !unsubscribed?(recipient) && options[:subscribed_checker].call(recipient)
70
+ processed = true
71
+ register_bounce bounce, m
72
+
73
+ bounce_count_per_type = bounce_count_for recipient, bounce.code
74
+ total_bounce_count = bounce_count_for recipient
75
+
76
+ rule = options[:bounce_counts_per_type_to_unsubscribe].select { |count, codes| codes.include?(bounce.code) }.first
77
+ max_count_per_type = rule ? rule.first : options[:default_bounce_count_per_type_to_unsubscribe]
78
+
79
+ bounce_count_per_type_exceeded = bounce_count_per_type >= max_count_per_type
80
+ total_bounce_count_exceeded = total_bounce_count >= options[:total_bounce_count_to_unsubscribe]
81
+
82
+ if bounce_count_per_type_exceeded || total_bounce_count_exceeded
83
+ logger.call "=> Unsubscribing #{recipient} and deleting #{bounces_for(recipient).size} bounced email(s)"
84
+
85
+ yield :recipient => recipient, :original_email => original_email, :bounce => bounce
86
+ unsubscribe recipient
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ if options[:delete_unprocessed_bounces] && !processed
93
+ m.delete unless test_mode?
94
+ deleted += 1
95
+ end
96
+ end
97
+ end
98
+
99
+ logger.call "Deleted #{deleted} unprocessed bounce(s) from the mailbox" if deleted > 0
100
+ connect_to_mailbox(options) { |pop| logger.call "Messages left in the mailbox: #{pop.mails.size}" }
101
+ end
102
+
103
+ def test_mode?
104
+ @@test_mode
105
+ end
106
+
107
+ def test_mode=(mode)
108
+ @@test_mode = mode
109
+ end
110
+
111
+ private
112
+
113
+ def connect_to_mailbox(options, &block)
114
+ Net::POP3.start(options[:server], options[:port], options[:username], options[:password], &block)
115
+ end
116
+
117
+ def unsubscribed?(recipient)
118
+ @@unsubscribed[recipient]
119
+ end
120
+
121
+ def unsubscribe(recipient)
122
+ @@unsubscribed[recipient] = true
123
+
124
+ bounces_for(recipient).each do |pop_mail|
125
+ pop_mail.delete unless test_mode?
126
+ end
127
+
128
+ @@emails_bounces[recipient] = nil
129
+ end
130
+
131
+ def register_bounce(bounce, pop_mail)
132
+ increment_bounce_count_for recipient, bounce.code
133
+ bounces_for(recipient) << pop_mail
134
+ end
135
+
136
+ def bounces_for(recipient)
137
+ @@emails_bounces[recipient] ||= []
138
+ end
139
+
140
+ def bounce_count_for(email, error_code = nil)
141
+ key = [email, error_code]
142
+ @@bounce_counts[key]
143
+ end
144
+
145
+ def increment_bounce_count_for(email, error_code = nil)
146
+ [error_code, nil].uniq.each do |code|
147
+ @@bounce_counts[[email, code]] += 1
148
+ end
149
+ end
150
+
151
+ def sent_message_from_bounce(mail)
152
+ to = mail.to.select { |address| address.to_s.start_with? Smailer::BOUNCES_PREFIX }.first
153
+ return nil if to.nil?
154
+
155
+ key = to.strip.split('@').first[Smailer::BOUNCES_PREFIX.size..-1]
156
+
157
+ @@keys_messages[key] ||= Smailer::Models::FinishedMail.where(:key => key).first
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -55,7 +55,7 @@ module Smailer
55
55
  # compute the VERP'd return_path if requested
56
56
  # or fall-back to a global return-path if not
57
57
  item_return_path = if options[:verp]
58
- "bounces-#{queue_item.key}@#{options[:return_path_domain]}"
58
+ "#{Smailer::BOUNCES_PREFIX}#{queue_item.key}@#{options[:return_path_domain]}"
59
59
  else
60
60
  options[:return_path]
61
61
  end
@@ -1,7 +1,7 @@
1
1
  module Smailer
2
2
  MAJOR = 0
3
- MINOR = 4
4
- PATCH = 4
3
+ MINOR = 5
4
+ PATCH = 0
5
5
 
6
6
  VERSION = [MAJOR, MINOR, PATCH].join('.')
7
7
  end
data/smailer.gemspec CHANGED
@@ -16,6 +16,7 @@ Gem::Specification.new do |s|
16
16
 
17
17
  s.add_development_dependency "bundler", ">= 1.0.0"
18
18
  s.add_runtime_dependency "mail", "~> 2.2"
19
+ s.add_runtime_dependency "bounce_email", "~> 0.2"
19
20
 
20
21
  s.files = `git ls-files`.split("\n")
21
22
  s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smailer
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 4
9
- - 4
10
- version: 0.4.4
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Dimitar Dimitrov
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-10-15 00:00:00 +02:00
18
+ date: 2011-10-27 00:00:00 +03:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -49,6 +49,21 @@ dependencies:
49
49
  version: "2.2"
50
50
  type: :runtime
51
51
  version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: bounce_email
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 15
61
+ segments:
62
+ - 0
63
+ - 2
64
+ version: "0.2"
65
+ type: :runtime
66
+ version_requirements: *id003
52
67
  description: A simple mailer for newsletters with basic campaign management, queue and unsubscribe support. To be used within Rails.
53
68
  email:
54
69
  - wireman@gmail.com
@@ -70,6 +85,7 @@ files:
70
85
  - lib/generators/smailer/templates/migration.rb.erb
71
86
  - lib/smailer.rb
72
87
  - lib/smailer/compatibility.rb
88
+ - lib/smailer/constants.rb
73
89
  - lib/smailer/models.rb
74
90
  - lib/smailer/models/finished_mail.rb
75
91
  - lib/smailer/models/mail_campaign.rb
@@ -78,6 +94,7 @@ files:
78
94
  - lib/smailer/models/property.rb
79
95
  - lib/smailer/models/queued_mail.rb
80
96
  - lib/smailer/tasks.rb
97
+ - lib/smailer/tasks/process_bounces.rb
81
98
  - lib/smailer/tasks/send.rb
82
99
  - lib/smailer/version.rb
83
100
  - rails/init.rb