sespool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
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,36 @@
1
+ # Sespool [![Build Status](https://travis-ci.org/ags/sespool.png?branch=master)](https://travis-ci.org/ags/sespool)
2
+
3
+ Bounce parser for Amazon SES SNS notifications. Currently only supports JSON.
4
+
5
+ ## Usage
6
+
7
+ ```ruby
8
+ >> bounce = Sespool::Bounce.new("raw sns json request body")
9
+ >> bounce.type
10
+ => 'Transient'
11
+ >> bounce.bounced_recipients.first.email_address
12
+ => 'foo@example.com'
13
+ >> bounce.mail.message_id
14
+ => '123-456-789'
15
+ ```
16
+
17
+ ## Example Integration
18
+
19
+ In a Rails app, you might want to setup your SES SNS bounces be be handled
20
+ by something like the following:
21
+
22
+ ```ruby
23
+ class SnsController < ApplicationController
24
+
25
+ def bounce_notification
26
+ # parse the SNS bounce JSON
27
+ bounce = Sespool::Bounce.new(request.raw_post)
28
+
29
+ # do something with it, probably persist it
30
+ EmailBounce.create_from_sns_message(bounce)
31
+
32
+ render nothing: true, status: 204
33
+ end
34
+
35
+ end
36
+ ```
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+ require 'bundler/gem_tasks'
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task default: :spec
data/lib/sespool.rb ADDED
@@ -0,0 +1,4 @@
1
+ require_relative 'sespool/bounce'
2
+
3
+ module Sespool
4
+ end
@@ -0,0 +1,63 @@
1
+ require 'json'
2
+ require 'time'
3
+
4
+ module Sespool
5
+ class Bounce
6
+
7
+ def initialize(raw_data)
8
+ message = JSON.parse(JSON.parse(raw_data).fetch('Message'))
9
+ @_mail_data = message.fetch('mail')
10
+ @_bounce_data = message.fetch('bounce')
11
+ end
12
+
13
+ def type
14
+ @_bounce_data.fetch('bounceType')
15
+ end
16
+
17
+ def sub_type
18
+ @_bounce_data.fetch('bounceSubType')
19
+ end
20
+
21
+ def reporting_mta
22
+ @_bounce_data.fetch('reportingMTA')
23
+ end
24
+
25
+ def feedback_id
26
+ @_bounce_data.fetch('feedbackId')
27
+ end
28
+
29
+ def timestamp
30
+ Time.parse(@_bounce_data.fetch('timestamp'))
31
+ end
32
+
33
+ def bounced_recipients
34
+ @_bounced_recipients ||= @_bounce_data.fetch('bouncedRecipients').map { |recipient|
35
+ Recipient.new(
36
+ recipient.fetch('emailAddress'),
37
+ recipient.fetch('status'),
38
+ recipient.fetch('diagnosticCode'),
39
+ recipient.fetch('action')
40
+ )
41
+ }
42
+ end
43
+
44
+ def mail
45
+ @_mail ||= Mail.new(
46
+ @_mail_data.fetch('timestamp'),
47
+ @_mail_data.fetch('source'),
48
+ @_mail_data.fetch('messageId'),
49
+ @_mail_data.fetch('destination')
50
+ )
51
+ end
52
+
53
+ class Recipient < Struct.new(:email_address, :status, :diagnostic_code, :action)
54
+ end
55
+
56
+ class Mail < Struct.new(:raw_timestamp, :source, :message_id, :destination)
57
+ def timestamp
58
+ Time.parse(raw_timestamp)
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module Sespool
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,11 @@
1
+ {
2
+ "Type" : "Notification",
3
+ "MessageId" : "1a509979-dede-56ab-9jbf-d36983a66cba",
4
+ "TopicArn" : "arn:aws:sns:us-east-1:535280530538:email_bounces",
5
+ "Message" : "{\"notificationType\":\"Bounce\",\"bounce\":{\"bounceSubType\":\"General\",\"bounceType\":\"Transient\",\"reportingMTA\":\"dsn; aws-ses-mta-svc-iad-1a-i-e6e8e98c.us-east-1.amazon.com\",\"bouncedRecipients\":[{\"emailAddress\":\"foo@example.com\",\"status\":\"4.4.7\",\"diagnosticCode\":\"smtp; 550 4.4.7 Message expired: unable to deliver in 840 minutes.<421 4.4.0 Unable to lookup DNS for example.com>\",\"action\":\"failed\"}],\"timestamp\":\"2013-05-17T16:26:31.000Z\",\"feedbackId\":\"0000013eb3506972-88979901-bf0e-31e4-b280-c390f3ef4cd7-000000\"},\"mail\":{\"timestamp\":\"2013-05-17T02:02:43.000Z\",\"source\":\"The sender <sender@example.com>\",\"messageId\":\"0000013eb0399348-00ca18d9-5995-4898-17a6-26f5ea0dbea3-000000\",\"destination\":[\"foo@example.com\",\"bar@example.com\"]}}",
6
+ "Timestamp" : "2013-05-17T16:26:32.155Z",
7
+ "SignatureVersion" : "1",
8
+ "Signature" : "qZl8NLEsr6R0g60V3D8WfWOcJmSahLAwHAIMaPgjBQMBuDmQB8mfaKrHTjnA4UWYVePc2xUfP3R1lmIPNQJ5Ug/qRZivrcumJkuMkOk3+KKFwD0hduTltQ8XhMgXFhLGC7qQ6XM3mKilrYvukz8Erk6E4JXOQEtCG0sp18R1g4M=",
9
+ "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe72b5f29f86de52f.pem",
10
+ "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:545230530538:email_bounces:72f38fee-9106-4423-9fd8-b93631723137"
11
+ }
@@ -0,0 +1,108 @@
1
+ require './lib/sespool'
2
+
3
+ def load_fixture(name)
4
+ File.open('./spec/fixtures/' + name).read
5
+ end
6
+
7
+ describe Sespool::Bounce do
8
+ let(:fixture) { load_fixture('fixture_a.json') }
9
+ subject(:bounce) { Sespool::Bounce.new(fixture) }
10
+
11
+ describe "#type" do
12
+ it "returns the bounce type" do
13
+ expect(bounce.type).to eq('Transient')
14
+ end
15
+ end
16
+
17
+ describe "#sub_type" do
18
+ it "returns the bounce sub type" do
19
+ expect(bounce.sub_type).to eq('General')
20
+ end
21
+ end
22
+
23
+ describe "#reporting_mta" do
24
+ it "returns the bounce reporting MTA" do
25
+ expect(bounce.reporting_mta).to \
26
+ eq('dsn; aws-ses-mta-svc-iad-1a-i-e6e8e98c.us-east-1.amazon.com')
27
+ end
28
+ end
29
+
30
+ describe "#timestamp" do
31
+ it "returns the bounce timestamp" do
32
+ expect(bounce.timestamp).to eq(Time.parse('2013-05-17T16:26:31.000Z'))
33
+ end
34
+ end
35
+
36
+ describe "#feedback_id" do
37
+ it "returns the bounce feedback id" do
38
+ expect(bounce.feedback_id).to \
39
+ eq('0000013eb3506972-88979901-bf0e-31e4-b280-c390f3ef4cd7-000000')
40
+ end
41
+ end
42
+
43
+ describe "#bounced_recipients" do
44
+ it "returns a list of bounced recipients" do
45
+ expect(bounce.bounced_recipients.size).to eq(1)
46
+ end
47
+
48
+ describe "Bounce::Recipient" do
49
+ subject(:recipient) { bounce.bounced_recipients.first }
50
+
51
+ describe "#email_address" do
52
+ it "returns the recipient email address" do
53
+ expect(recipient.email_address).to eq('foo@example.com')
54
+ end
55
+ end
56
+
57
+ describe "#status" do
58
+ it "returns the recipient status" do
59
+ expect(recipient.status).to eq('4.4.7')
60
+ end
61
+ end
62
+
63
+ describe "#diagnostic_code" do
64
+ it "returns the recipient diagnostic code" do
65
+ expect(recipient.diagnostic_code).to eq(
66
+ 'smtp; 550 4.4.7 Message expired: unable to deliver in 840 minutes.'\
67
+ '<421 4.4.0 Unable to lookup DNS for example.com>',
68
+ )
69
+ end
70
+ end
71
+
72
+ describe "#action" do
73
+ it "returns the recipient action" do
74
+ expect(recipient.action).to eq('failed')
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ describe "#mail" do
81
+ describe "#timestamp" do
82
+ it "returns the mail timestamp" do
83
+ expect(bounce.mail.timestamp).to \
84
+ eq(Time.parse('2013-05-17T02:02:43.000Z'))
85
+ end
86
+ end
87
+
88
+ describe "#source" do
89
+ it "returns the sender" do
90
+ expect(bounce.mail.source).to eq('The sender <sender@example.com>')
91
+ end
92
+ end
93
+
94
+ describe "#message_id" do
95
+ it "returns the message ID" do
96
+ expect(bounce.mail.message_id).to \
97
+ eq('0000013eb0399348-00ca18d9-5995-4898-17a6-26f5ea0dbea3-000000')
98
+ end
99
+ end
100
+
101
+ describe "#destination" do
102
+ it "returns a list of intended recipients" do
103
+ expect(bounce.mail.destination).to \
104
+ eq(['foo@example.com', 'bar@example.com'])
105
+ end
106
+ end
107
+ end
108
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sespool
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alex Smith
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Bounce parser for Amazon SES SNS notifications
63
+ email:
64
+ - alex@thatalexguy.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - lib/sespool/bounce.rb
70
+ - lib/sespool/version.rb
71
+ - lib/sespool.rb
72
+ - MIT-LICENSE
73
+ - Rakefile
74
+ - README.md
75
+ - spec/fixtures/fixture_a.json
76
+ - spec/lib/sespool/bounce_spec.rb
77
+ homepage: https://github.com/ags/sespool
78
+ licenses:
79
+ - MIT
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 1.8.23
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: Bounce parser for Amazon SES SNS notifications
102
+ test_files:
103
+ - spec/fixtures/fixture_a.json
104
+ - spec/lib/sespool/bounce_spec.rb
105
+ has_rdoc: