bounce_fetcher 0.0.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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +27 -0
- data/Rakefile +11 -0
- data/bounce_fetcher.gemspec +24 -0
- data/lib/bounce_fetcher.rb +39 -0
- data/lib/bounce_fetcher/bounce_detector.rb +14 -0
- data/lib/bounce_fetcher/email_extractor.rb +13 -0
- data/lib/bounce_fetcher/pop_fetcher.rb +54 -0
- data/lib/bounce_fetcher/version.rb +3 -0
- data/test/test_bounce_detector.rb +44 -0
- data/test/test_bounce_fetcher.rb +56 -0
- data/test/test_email_extractor.rb +18 -0
- metadata +86 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Michael Guterl
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Bounce Fetcher
|
2
|
+
|
3
|
+
A set of tools for processing email bounces in a flexible way.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
bounces = BounceFetcher.pop3('pop3.host.com', 'username', 'password')
|
8
|
+
bounces.each do |email|
|
9
|
+
# do something application specific
|
10
|
+
# For example: User.bounce_email(email)
|
11
|
+
end
|
12
|
+
|
13
|
+
This will fetch each email from the POP3 server specified, if the email is a determined to be a permanent bounce, we will attempt to determine what email address the bounce is for, yield it to the block, and the email will be deleted from the POP3 server.
|
14
|
+
|
15
|
+
## Note on Patches/Pull Requests
|
16
|
+
|
17
|
+
* Fork the project.
|
18
|
+
* Make your feature addition or bug fix.
|
19
|
+
* Add tests for it. This is important so I don't break it in a
|
20
|
+
future version unintentionally.
|
21
|
+
* Commit, do not mess with rakefile, version, or history.
|
22
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
23
|
+
* Send me a pull request. Bonus points for topic branches.
|
24
|
+
|
25
|
+
## Copyright
|
26
|
+
|
27
|
+
Copyright (c) 2011 Michael Guterl. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "bounce_fetcher/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "bounce_fetcher"
|
7
|
+
s.version = BounceFetcher::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Michael Guterl"]
|
10
|
+
s.email = ["michael@diminishing.org"]
|
11
|
+
s.homepage = "http://github.com/mguterl/bounce_fetcher"
|
12
|
+
s.summary = %q{A set of tools for processing email bounces in a flexible way.}
|
13
|
+
s.description = %q{A set of tools for processing email bounces in a flexible way.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "bounce_fetcher"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency 'tmail'
|
23
|
+
s.add_dependency 'bounce-email', '0.0.1'
|
24
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module BounceFetcher
|
2
|
+
|
3
|
+
def self.new(*args, &block)
|
4
|
+
BounceFetcher.new(*args, &block)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.pop3(host, username, password)
|
8
|
+
fetcher = PopFetcher.new(host, username, password)
|
9
|
+
extractor = Extractor.new
|
10
|
+
detector = Detector.new
|
11
|
+
new(fetcher, extractor, detector)
|
12
|
+
end
|
13
|
+
|
14
|
+
class BounceFetcher
|
15
|
+
|
16
|
+
def initialize(fetcher, extractor, detector)
|
17
|
+
@fetcher = fetcher
|
18
|
+
@extractor = extractor
|
19
|
+
@detector = detector
|
20
|
+
end
|
21
|
+
|
22
|
+
def each
|
23
|
+
@fetcher.each do |e|
|
24
|
+
if @detector.permanent_bounce?(e)
|
25
|
+
@extractor.extract_emails(e).each do |email|
|
26
|
+
yield email
|
27
|
+
end
|
28
|
+
e.delete
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
require 'bounce_fetcher/pop_fetcher'
|
38
|
+
require 'bounce_fetcher/email_extractor'
|
39
|
+
require 'bounce_fetcher/bounce_detector'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# lifted from the action_mailer_verp plugin:
|
2
|
+
# http://github.com/jamesgolick/action_mailer_verp
|
3
|
+
|
4
|
+
module BounceProcessor
|
5
|
+
|
6
|
+
class PopFetcher
|
7
|
+
|
8
|
+
class Email
|
9
|
+
def initialize(email)
|
10
|
+
@email = email
|
11
|
+
end
|
12
|
+
|
13
|
+
def delete
|
14
|
+
@email.delete
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(meth, *args, &block)
|
18
|
+
parsed_email.send(meth, *args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def parsed_email
|
24
|
+
@parsed_email ||= TMail::Mail.parse(@email.pop)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(host, username, password)
|
29
|
+
@host = host
|
30
|
+
@username = username
|
31
|
+
@password = password
|
32
|
+
end
|
33
|
+
|
34
|
+
def each
|
35
|
+
connection.each_mail do |e|
|
36
|
+
yield Email.new(e)
|
37
|
+
end
|
38
|
+
connection.finish
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def connection
|
44
|
+
if @connection.nil?
|
45
|
+
@connection = Net::POP3.new(@host)
|
46
|
+
@connection.start(@username, @password)
|
47
|
+
end
|
48
|
+
|
49
|
+
@connection
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'bounce_fetcher'
|
3
|
+
|
4
|
+
class TestBounceDetector < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
class Email < Struct.new(:subject, :body)
|
7
|
+
def parts
|
8
|
+
[]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class EmailWithBounceStatus
|
13
|
+
class Part < Struct.new(:body); end
|
14
|
+
|
15
|
+
def subject
|
16
|
+
"mail delivery failed"
|
17
|
+
end
|
18
|
+
|
19
|
+
def parts
|
20
|
+
[nil, Part.new("Status: 5.2.0")]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup
|
25
|
+
@detector = BounceFetcher::BounceDetector.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_permanent_bounce_with_temporary_failure
|
29
|
+
assert !@detector.permanent_bounce?(Email.new("mail delivery failed"))
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_permanent_bounce_with_out_of_town_auto_responder
|
33
|
+
assert !@detector.permanent_bounce?(Email.new("Out of Town"))
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_permanent_bounce_with_a_permanent_bounce_body
|
37
|
+
assert @detector.permanent_bounce?(Email.new("mail delivery failed", "mailbox unavailable"))
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_permanent_bounce_with_a_permanent_bounce_status
|
41
|
+
assert @detector.permanent_bounce?(EmailWithBounceStatus.new)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'bounce_fetcher'
|
3
|
+
|
4
|
+
class TestBounceFetcher < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
class FakePopFetcher
|
7
|
+
def initialize(*emails)
|
8
|
+
@emails = emails
|
9
|
+
end
|
10
|
+
|
11
|
+
def each
|
12
|
+
@emails.each { |email| yield email }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Email < Struct.new(:body, :permanent_bounce)
|
17
|
+
def delete
|
18
|
+
@deleted = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def deleted?
|
22
|
+
@deleted
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class FakeExtractor
|
27
|
+
def extract_emails(tmail)
|
28
|
+
[tmail.body]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class FakeBounceDetector
|
33
|
+
def permanent_bounce?(tmail)
|
34
|
+
tmail.permanent_bounce
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_each_only_returns_emails_for_permanent_bounces
|
39
|
+
permanent_bounce = Email.new('foo@bar.com', true)
|
40
|
+
temporary_bounce = Email.new('bar@baz.com', false)
|
41
|
+
pop_fetcher = FakePopFetcher.new(permanent_bounce, temporary_bounce)
|
42
|
+
extractor = FakeExtractor.new
|
43
|
+
detector = FakeBounceDetector.new
|
44
|
+
fetcher = BounceFetcher.new(pop_fetcher, extractor, detector)
|
45
|
+
|
46
|
+
emails = []
|
47
|
+
fetcher.each do |email|
|
48
|
+
emails << email
|
49
|
+
end
|
50
|
+
|
51
|
+
assert_equal ['foo@bar.com'], emails
|
52
|
+
assert permanent_bounce.deleted?
|
53
|
+
assert !temporary_bounce.deleted?
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'bounce_fetcher'
|
3
|
+
|
4
|
+
class TestEmailExtractor < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@extractor = BounceFetcher::EmailExtractor.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_extract_emails
|
11
|
+
assert_equal ['foo@bar.com', 'bar@baz.com'], @extractor.extract_emails("foo@bar.com\nbar@baz.com")
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_extract_emails_ignores_duplicates
|
15
|
+
assert_equal ['foo@bar.com'], @extractor.extract_emails("foo@bar.com\nfoo@bar.com")
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bounce_fetcher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael Guterl
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-04-06 00:00:00.000000000 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: tmail
|
17
|
+
requirement: &2162141280 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2162141280
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: bounce-email
|
28
|
+
requirement: &2162140740 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - =
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.0.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *2162140740
|
37
|
+
description: A set of tools for processing email bounces in a flexible way.
|
38
|
+
email:
|
39
|
+
- michael@diminishing.org
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- Gemfile
|
46
|
+
- LICENSE
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- bounce_fetcher.gemspec
|
50
|
+
- lib/bounce_fetcher.rb
|
51
|
+
- lib/bounce_fetcher/bounce_detector.rb
|
52
|
+
- lib/bounce_fetcher/email_extractor.rb
|
53
|
+
- lib/bounce_fetcher/pop_fetcher.rb
|
54
|
+
- lib/bounce_fetcher/version.rb
|
55
|
+
- test/test_bounce_detector.rb
|
56
|
+
- test/test_bounce_fetcher.rb
|
57
|
+
- test/test_email_extractor.rb
|
58
|
+
has_rdoc: true
|
59
|
+
homepage: http://github.com/mguterl/bounce_fetcher
|
60
|
+
licenses: []
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project: bounce_fetcher
|
79
|
+
rubygems_version: 1.5.2
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: A set of tools for processing email bounces in a flexible way.
|
83
|
+
test_files:
|
84
|
+
- test/test_bounce_detector.rb
|
85
|
+
- test/test_bounce_fetcher.rb
|
86
|
+
- test/test_email_extractor.rb
|