smailer 0.4.4 → 0.5.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.
- data/lib/smailer.rb +3 -1
- data/lib/smailer/constants.rb +3 -0
- data/lib/smailer/tasks.rb +1 -0
- data/lib/smailer/tasks/process_bounces.rb +162 -0
- data/lib/smailer/tasks/send.rb +1 -1
- data/lib/smailer/version.rb +2 -2
- data/smailer.gemspec +1 -0
- metadata +22 -5
data/lib/smailer.rb
CHANGED
data/lib/smailer/tasks.rb
CHANGED
@@ -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
|
data/lib/smailer/tasks/send.rb
CHANGED
@@ -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
|
-
"
|
58
|
+
"#{Smailer::BOUNCES_PREFIX}#{queue_item.key}@#{options[:return_path_domain]}"
|
59
59
|
else
|
60
60
|
options[:return_path]
|
61
61
|
end
|
data/lib/smailer/version.rb
CHANGED
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:
|
4
|
+
hash: 11
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
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-
|
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
|