ses-proxy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/app/public/bootstrap/css/bootstrap-responsive.css +1092 -0
  2. data/app/public/bootstrap/css/bootstrap-responsive.min.css +9 -0
  3. data/app/public/bootstrap/css/bootstrap.css +6039 -0
  4. data/app/public/bootstrap/css/bootstrap.min.css +9 -0
  5. data/app/public/bootstrap/img/glyphicons-halflings-white.png +0 -0
  6. data/app/public/bootstrap/img/glyphicons-halflings.png +0 -0
  7. data/app/public/bootstrap/js/bootstrap.js +2159 -0
  8. data/app/public/bootstrap/js/bootstrap.min.js +6 -0
  9. data/app/public/css/application.css +16 -0
  10. data/app/public/datepicker/css/datepicker.css +7 -0
  11. data/app/public/datepicker/js/bootstrap-datepicker.js +454 -0
  12. data/app/public/datepicker/less/datepicker.less +119 -0
  13. data/app/public/highcharts/adapters/mootools-adapter.js +13 -0
  14. data/app/public/highcharts/adapters/mootools-adapter.src.js +327 -0
  15. data/app/public/highcharts/adapters/prototype-adapter.js +16 -0
  16. data/app/public/highcharts/adapters/prototype-adapter.src.js +385 -0
  17. data/app/public/highcharts/highcharts-more.js +35 -0
  18. data/app/public/highcharts/highcharts.js +246 -0
  19. data/app/public/highcharts/highcharts.src.js +15111 -0
  20. data/app/public/highcharts/modules/canvas-tools.js +133 -0
  21. data/app/public/highcharts/modules/canvas-tools.src.js +3113 -0
  22. data/app/public/highcharts/modules/data.js +11 -0
  23. data/app/public/highcharts/modules/data.src.js +277 -0
  24. data/app/public/highcharts/modules/exporting.js +23 -0
  25. data/app/public/highcharts/modules/exporting.src.js +736 -0
  26. data/app/public/highcharts/themes/dark-blue.js +263 -0
  27. data/app/public/highcharts/themes/dark-green.js +263 -0
  28. data/app/public/highcharts/themes/gray.js +262 -0
  29. data/app/public/highcharts/themes/grid.js +95 -0
  30. data/app/public/highcharts/themes/skies.js +89 -0
  31. data/app/public/images/loader.gif +0 -0
  32. data/app/public/js/application.js +81 -0
  33. data/app/views/_chart.haml +2 -0
  34. data/app/views/_search_form.haml +23 -0
  35. data/app/views/bounces.haml +23 -0
  36. data/app/views/kaminari/_first_page.html.erb +3 -0
  37. data/app/views/kaminari/_gap.html.erb +3 -0
  38. data/app/views/kaminari/_last_page.html.erb +3 -0
  39. data/app/views/kaminari/_next_page.html.erb +3 -0
  40. data/app/views/kaminari/_page.html.erb +3 -0
  41. data/app/views/kaminari/_paginator.html.erb +17 -0
  42. data/app/views/kaminari/_prev_page.html.erb +3 -0
  43. data/app/views/layout.haml +25 -0
  44. data/app/views/mails.haml +26 -0
  45. data/app/web_panel.rb +149 -0
  46. data/bin/ses_proxy +20 -0
  47. data/lib/ses_proxy/conf.rb +9 -0
  48. data/lib/ses_proxy/main_command.rb +154 -0
  49. data/lib/ses_proxy/models/bounce.rb +14 -0
  50. data/lib/ses_proxy/models/complaint.rb +13 -0
  51. data/lib/ses_proxy/models/email.rb +15 -0
  52. data/lib/ses_proxy/smtp_server.rb +122 -0
  53. data/lib/ses_proxy/sns_endpoint.rb +199 -0
  54. data/ses_proxy.rb +10 -0
  55. data/template/mongoid.yml +12 -0
  56. data/template/ses-proxy.yml +19 -0
  57. metadata +294 -0
@@ -0,0 +1,14 @@
1
+ require 'mongoid'
2
+
3
+ module SesProxy
4
+ class Bounce
5
+ include Mongoid::Document
6
+ store_in collection: "bounced"
7
+
8
+ field :email, type: String
9
+ field :type, type: String
10
+ field :desc, type: String
11
+ field :created_at, type: DateTime
12
+ field :updated_at, type: DateTime
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ require 'mongoid'
2
+
3
+ module SesProxy
4
+ class Complaint
5
+ include Mongoid::Document
6
+ store_in collection: "complained"
7
+
8
+ field :email, type: String
9
+ field :type, type: String
10
+ field :created_at, type: DateTime
11
+ field :updated_at, type: DateTime
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ require 'mongoid'
2
+
3
+ module SesProxy
4
+ class Email
5
+ include Mongoid::Document
6
+
7
+ field :sender, type: String
8
+ field :recipients, type: String
9
+ field :subject, type: String
10
+ field :body, type: String
11
+ field :system, type: String
12
+ field :created_at, type: DateTime
13
+ field :updated_at, type: DateTime
14
+ end
15
+ end
@@ -0,0 +1,122 @@
1
+ require 'eventmachine'
2
+ require 'mail'
3
+ require 'aws-sdk'
4
+
5
+ module SesProxy
6
+ class SmtpServer < EM::P::SmtpServer
7
+
8
+ def get_server_domain
9
+ @host || "mock.smtp.server.local"
10
+ end
11
+
12
+ def get_server_greeting
13
+ "smtp ses greetings"
14
+ end
15
+
16
+ def receive_plain_auth(user, password)
17
+ @verified = SesProxy::Conf.get[:smtp_auth][:user].eql?(user) and SesProxy::Conf.get[:smtp_auth][:password].eql?(password)
18
+ end
19
+
20
+ def receive_sender(sender)
21
+ sender = sender.gsub(/[<>]/,'')
22
+ @sender = sender
23
+ domains = ses.identities.domains.map(&:identity)
24
+ email_addresses = ses.identities.email_addresses.map(&:identity)
25
+ identity = nil
26
+ if email_addresses.include?(sender)
27
+ identity = ses.identities[sender]
28
+ elsif domains.include?(sender.split('@').last)
29
+ identity = ses.identities[sender.split('@').last]
30
+ end
31
+ identity&&identity.verified?
32
+ end
33
+
34
+ def receive_recipient(recipient)
35
+ recipients << recipient.gsub(/[<>]/,'')
36
+ true
37
+ end
38
+
39
+ def receive_data_chunk(data)
40
+ @message = "#{@message}\n#{data.join("\n")}"
41
+ true
42
+ end
43
+
44
+ def recipients
45
+ @recipients ||= []
46
+ end
47
+
48
+ def sender
49
+ @sender || ""
50
+ end
51
+
52
+ def message
53
+ @message || ""
54
+ end
55
+
56
+ def verified
57
+ @verified || false
58
+ end
59
+
60
+ def ses
61
+ @ses ||= AWS::SimpleEmailService.new(SesProxy::Conf.get[:aws])
62
+ end
63
+
64
+ def receive_message
65
+ return false unless verified
66
+ bounced = Bounce.where({:email=>{"$in"=>recipients}}).map(&:email)
67
+ mail = Mail.read_from_string(message)
68
+ #TODO: Define policy for retry when bounce is not permanent
69
+ actual_recipients = recipients - bounced
70
+ actual_cc_addrs = mail.cc_addrs - bounced
71
+ actual_bcc_addrs = mail.bcc_addrs - bounced
72
+ if actual_recipients.any?
73
+ mail.to = actual_recipients.uniq.join(",")
74
+ mail.cc = actual_cc_addrs.uniq.join(",")
75
+ mail.bcc = actual_bcc_addrs.uniq.join(",")
76
+ record = Email.new({
77
+ :sender => sender,
78
+ :recipients => actual_recipients.uniq.join(","),
79
+ :subject => mail.subject,
80
+ :body => mail.body.decoded,
81
+ :system => mail['X-Sender-System']||"Unknown",
82
+ :created_at => Time.now,
83
+ :updated_at => Time.now
84
+ })
85
+ record.save!
86
+ begin
87
+ ses.send_raw_email(mail.to_s)
88
+ true
89
+ rescue Exception => e
90
+ print "Error! "
91
+ puts e.message
92
+ false
93
+ end
94
+ else
95
+ puts "No valid recipients!"
96
+ true
97
+ end
98
+ end
99
+
100
+ def receive_ehlo_domain(domain)
101
+ @ehlo_domain = domain
102
+ true
103
+ end
104
+
105
+ def self.start(host, port)
106
+ trap(:QUIT) { stop }
107
+ @host = host
108
+ @server = EM.start_server host, port, self
109
+ end
110
+
111
+ def self.stop
112
+ if @server
113
+ EM.stop_server @server
114
+ @server = nil
115
+ end
116
+ end
117
+
118
+ def self.running?
119
+ !!@server
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,199 @@
1
+ require 'rack/request'
2
+ require 'json'
3
+ require 'aws-sdk'
4
+ require 'net/http'
5
+ require 'base64'
6
+ require 'openssl'
7
+
8
+ module SesProxy
9
+ class SnsEndpoint
10
+
11
+ CERTS_URI = {
12
+ "us-east-1" => "http://sns.us-east-1.amazonaws.com/SimpleNotificationService.pem",
13
+ "us-west-1" => "http://sns.us-west-1.amazonaws.com/SimpleNotificationService.pem",
14
+ "eu-west-1" => "http://sns.eu-west-1.amazonaws.com/SimpleNotificationService.pem",
15
+ "ap-southeast-1" => "http://sns.ap-southeast-1.amazonaws.com/SimpleNotificationService.pem"
16
+ }
17
+
18
+ def call(env)
19
+ req = Rack::Request.new(env)
20
+ res = []
21
+
22
+ if not req.post? or not check_message_type(env) or not check_topic(env)
23
+ return make_error
24
+ end
25
+
26
+ json_string = req.body.read
27
+ sns_obj = nil
28
+
29
+ begin
30
+ if json_string and not json_string.strip.eql?""
31
+ sns_obj = JSON.parse json_string
32
+ end
33
+ if sns_obj
34
+ unless check_message_signature(sns_obj)
35
+ puts "Error in message signature"
36
+ return make_error
37
+ end
38
+ if env["HTTP_X_AMZ_SNS_MESSAGE_TYPE"].eql?"Notification"
39
+ message = JSON.parse sns_obj["Message"]
40
+ unless message
41
+ return make_error
42
+ end
43
+ if message["notificationType"].eql? "Bounce"
44
+ message["bounce"]["bouncedRecipients"].each do |recipient|
45
+ if record = Bounce.where(:email => recipient["emailAddress"]).first
46
+ record.updated_at = Time.now
47
+ record.save!
48
+ else
49
+ record = Bounce.new({
50
+ :email => recipient["emailAddress"],
51
+ :type => message["bounce"]["bounceType"],
52
+ :desc => recipient["diagnosticCode"],
53
+ :created_at => Time.now,
54
+ :updated_at => Time.now
55
+ })
56
+ record.save!
57
+ end
58
+ end
59
+ elsif message["notificationType"].eql? "Complaint"
60
+ message["complaint"]["complainedRecipients"].each do |recipient|
61
+ if record = Complaint.where(:email => recipient["emailAddress"]).first
62
+ record.updated_at = Time.now
63
+ record.save!
64
+ else
65
+ record = Complaint.new({
66
+ :email=>recipient["emailAddress"],
67
+ :type=>message["complaint"]["complaintFeedbackType"],
68
+ :created_at=>Time.now,
69
+ :updated_at=>Time.now
70
+ })
71
+ record.save!
72
+ end
73
+ end
74
+ end
75
+ elsif env["HTTP_X_AMZ_SNS_MESSAGE_TYPE"].eql?"SubscriptionConfirmation" and sns_obj["Type"].eql? "SubscriptionConfirmation"
76
+ sns.confirm_subscription :topic_arn=>sns_obj["TopicArn"], :token=>sns_obj["Token"], :authenticate_on_unsubscribe=>"true"
77
+ end
78
+ [200, {'Content-Type' => 'text/html'}, res]
79
+ else
80
+ puts "SNS Object is nil"
81
+ return make_error
82
+ end
83
+ rescue Exception => e
84
+ print "Error! "
85
+ puts e.message
86
+ return make_error
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def check_message_signature(sns_obj)
93
+ if sns_obj["Type"].eql? "Notification"
94
+ string = get_notification_canonical_string(sns_obj)
95
+ else
96
+ string = get_subscription_confirmation_canonical_string(sns_obj)
97
+ end
98
+ region = sns_obj["TopicArn"].split(":")[3]
99
+ signature = sns_obj["Signature"]
100
+ #openssl x509 -in CERT -pubkey -noout > pub_key
101
+ pub_key = OpenSSL::X509::Certificate.new(File.read(get_cert(region))).public_key
102
+ #base64 -i -d signature > plain_signature
103
+ plain_signature = Base64.decode64(signature)
104
+ #openssl dgst -sha1 -verify pub -signature sigraw MESS
105
+ pub_key.verify(OpenSSL::Digest::SHA1.new, plain_signature, string)
106
+ end
107
+
108
+ def get_cert(region)
109
+ unless File.exists? File.join("/","tmp","#{region}_sns_cert.pem")
110
+ res = fetch CERTS_URI[region]
111
+ puts res.inspect
112
+ if res and not res.eql?""
113
+ open(File.join("/","tmp","#{region}_sns_cert.pem"), "wb") do |file|
114
+ file.write(res)
115
+ end
116
+ else
117
+ raise Exception, "Unable to get SNS Certificate!"
118
+ end
119
+ end
120
+ File.join("/","tmp","#{region}_sns_cert.pem")
121
+ end
122
+
123
+ def get_notification_canonical_string(sns_obj)
124
+ string = "Message\n"
125
+ string = "#{string}#{sns_obj["Message"]}\n"
126
+ string = "#{string}MessageId\n"
127
+ string = "#{string}#{sns_obj["MessageId"]}\n"
128
+ if sns_obj["Subject"]
129
+ string = "#{string}Subject\n"
130
+ string = "#{string}#{sns_obj["Subject"]}\n"
131
+ end
132
+ string = "#{string}Timestamp\n"
133
+ string = "#{string}#{sns_obj["Timestamp"]}\n"
134
+ string = "#{string}TopicArn\n"
135
+ string = "#{string}#{sns_obj["TopicArn"]}\n"
136
+ string = "#{string}Type\n"
137
+ string = "#{string}#{sns_obj["Type"]}\n"
138
+ string.force_encoding("UTF-8")
139
+ end
140
+
141
+ def get_subscription_confirmation_canonical_string(sns_obj)
142
+ string = "Message\n"
143
+ string = "#{string}#{sns_obj["Message"]}\n"
144
+ string = "#{string}MessageId\n"
145
+ string = "#{string}#{sns_obj["MessageId"]}\n"
146
+ string = "#{string}SubscribeURL\n"
147
+ string = "#{string}#{sns_obj["SubscribeURL"]}\n"
148
+ string = "#{string}Timestamp\n"
149
+ string = "#{string}#{sns_obj["Timestamp"]}\n"
150
+ string = "#{string}Token\n"
151
+ string = "#{string}#{sns_obj["Token"]}\n"
152
+ string = "#{string}TopicArn\n"
153
+ string = "#{string}#{sns_obj["TopicArn"]}\n"
154
+ string = "#{string}Type\n"
155
+ string = "#{string}#{sns_obj["Type"]}\n"
156
+ string.force_encoding("UTF-8")
157
+ end
158
+
159
+ def fetch(uri_str, limit = 10)
160
+ raise Exception, 'too many HTTP redirects' if limit == 0
161
+
162
+ response = Net::HTTP.get_response(URI(uri_str))
163
+
164
+ case response
165
+ when Net::HTTPSuccess then
166
+ response.body
167
+ when Net::HTTPRedirection then
168
+ puts response.inspect
169
+ location = response['location']
170
+ url = URI.parse(location)
171
+ location = "#{URI(uri_str).scheme}://#{URI(uri_str).host}#{location}" unless url.host
172
+ warn "redirected to #{location}"
173
+ fetch(location, limit - 1)
174
+ else
175
+ response.value
176
+ end
177
+ end
178
+
179
+ def make_error
180
+ [422, {'Content-Type' => 'text/html'}, ["Wrong request!"]]
181
+ end
182
+
183
+ def sns
184
+ @sns ||= AWS::SNS::Client.new(SesProxy::Conf.get[:aws])
185
+ end
186
+
187
+ def check_topic(env)
188
+ topic_arn = env["HTTP_X_AMZ_SNS_TOPIC_ARN"]
189
+ allowed_topic_arns = SesProxy::Conf.get[:aws][:allowed_topic_arns]
190
+ topic_arn && allowed_topic_arns.include?(topic_arn)
191
+ end
192
+
193
+ def check_message_type(env)
194
+ message_type = env["HTTP_X_AMZ_SNS_MESSAGE_TYPE"]
195
+ message_type && ["SubscriptionConfirmation","Notification","UnsubscribeConfirmation"].include?(message_type)
196
+ end
197
+
198
+ end
199
+ end
@@ -0,0 +1,10 @@
1
+ module SesProxy
2
+ ROOT = File.expand_path(File.join(File.dirname(__FILE__)))
3
+ autoload :MainCommand, 'ses_proxy/main_command'
4
+ autoload :SnsEndpoint, 'ses_proxy/sns_endpoint'
5
+ autoload :SmtpServer, 'ses_proxy/smtp_server'
6
+ autoload :Conf, 'ses_proxy/conf'
7
+ autoload :Bounce, 'ses_proxy/models/bounce'
8
+ autoload :Complaint, 'ses_proxy/models/complaint'
9
+ autoload :Email, 'ses_proxy/models/email'
10
+ end
@@ -0,0 +1,12 @@
1
+ development:
2
+ sessions:
3
+ default:
4
+ database: ses-proxy-development
5
+ hosts:
6
+ - localhost:27017
7
+ production:
8
+ sessions:
9
+ default:
10
+ database: ses-proxy-production
11
+ hosts:
12
+ - localhost:27017
@@ -0,0 +1,19 @@
1
+ :aws:
2
+ :access_key_id: your_access_key_id
3
+ :secret_access_key: your_secret_access_key
4
+ :account_id: 000000000000
5
+ :allowed_topic_arns: ["arn:aws:sns:us-east-1:123456789012:MyTopic"]
6
+ :smtp_auth:
7
+ :user: smtp_user
8
+ :password: smtp_pass
9
+ :test:
10
+ :from: test@example.com
11
+ :to: ["test@example.com"]
12
+ :topic_arn: "arn:aws:sns:us-east-1:123456789012:MyTopic"
13
+ :subscription_arn: "arn:aws:sns:us-east-1:123456789012:MyTopic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55"
14
+ #:smtp:
15
+ # :port: 1025
16
+ # :host: 0.0.0.0
17
+ #:http:
18
+ # :port: 9292
19
+ # :host: 0.0.0.0
metadata ADDED
@@ -0,0 +1,294 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ses-proxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Massimo Maino
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: clamp
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
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: rack
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
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
+ - !ruby/object:Gem::Dependency
63
+ name: thin
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: aws-sdk
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: mail
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: eventmachine
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: mongoid
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :runtime
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: haml
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :runtime
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ - !ruby/object:Gem::Dependency
159
+ name: sinatra
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :runtime
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ - !ruby/object:Gem::Dependency
175
+ name: kaminari
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ type: :runtime
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ - !ruby/object:Gem::Dependency
191
+ name: padrino-helpers
192
+ requirement: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ! '>='
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ type: :runtime
199
+ prerelease: false
200
+ version_requirements: !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ! '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ description:
207
+ email: maintux@gmail.com
208
+ executables:
209
+ - ses_proxy
210
+ extensions: []
211
+ extra_rdoc_files: []
212
+ files:
213
+ - ses_proxy.rb
214
+ - lib/ses_proxy/sns_endpoint.rb
215
+ - lib/ses_proxy/models/email.rb
216
+ - lib/ses_proxy/models/complaint.rb
217
+ - lib/ses_proxy/models/bounce.rb
218
+ - lib/ses_proxy/conf.rb
219
+ - lib/ses_proxy/smtp_server.rb
220
+ - lib/ses_proxy/main_command.rb
221
+ - bin/ses_proxy
222
+ - app/web_panel.rb
223
+ - app/views/_search_form.haml
224
+ - app/views/bounces.haml
225
+ - app/views/kaminari/_gap.html.erb
226
+ - app/views/kaminari/_prev_page.html.erb
227
+ - app/views/kaminari/_page.html.erb
228
+ - app/views/kaminari/_next_page.html.erb
229
+ - app/views/kaminari/_paginator.html.erb
230
+ - app/views/kaminari/_first_page.html.erb
231
+ - app/views/kaminari/_last_page.html.erb
232
+ - app/views/layout.haml
233
+ - app/views/mails.haml
234
+ - app/views/_chart.haml
235
+ - app/public/datepicker/less/datepicker.less
236
+ - app/public/datepicker/css/datepicker.css
237
+ - app/public/datepicker/js/bootstrap-datepicker.js
238
+ - app/public/images/loader.gif
239
+ - app/public/css/application.css
240
+ - app/public/js/application.js
241
+ - app/public/highcharts/modules/canvas-tools.js
242
+ - app/public/highcharts/modules/exporting.js
243
+ - app/public/highcharts/modules/data.src.js
244
+ - app/public/highcharts/modules/data.js
245
+ - app/public/highcharts/modules/exporting.src.js
246
+ - app/public/highcharts/modules/canvas-tools.src.js
247
+ - app/public/highcharts/highcharts.src.js
248
+ - app/public/highcharts/themes/gray.js
249
+ - app/public/highcharts/themes/skies.js
250
+ - app/public/highcharts/themes/dark-blue.js
251
+ - app/public/highcharts/themes/grid.js
252
+ - app/public/highcharts/themes/dark-green.js
253
+ - app/public/highcharts/adapters/prototype-adapter.js
254
+ - app/public/highcharts/adapters/prototype-adapter.src.js
255
+ - app/public/highcharts/adapters/mootools-adapter.src.js
256
+ - app/public/highcharts/adapters/mootools-adapter.js
257
+ - app/public/highcharts/highcharts-more.js
258
+ - app/public/highcharts/highcharts.js
259
+ - app/public/bootstrap/img/glyphicons-halflings-white.png
260
+ - app/public/bootstrap/img/glyphicons-halflings.png
261
+ - app/public/bootstrap/css/bootstrap.min.css
262
+ - app/public/bootstrap/css/bootstrap-responsive.css
263
+ - app/public/bootstrap/css/bootstrap-responsive.min.css
264
+ - app/public/bootstrap/css/bootstrap.css
265
+ - app/public/bootstrap/js/bootstrap.min.js
266
+ - app/public/bootstrap/js/bootstrap.js
267
+ - template/ses-proxy.yml
268
+ - template/mongoid.yml
269
+ homepage: https://github.com/maintux/ses-proxy
270
+ licenses:
271
+ - MIT
272
+ post_install_message:
273
+ rdoc_options: []
274
+ require_paths:
275
+ - lib
276
+ required_ruby_version: !ruby/object:Gem::Requirement
277
+ none: false
278
+ requirements:
279
+ - - ! '>='
280
+ - !ruby/object:Gem::Version
281
+ version: '0'
282
+ required_rubygems_version: !ruby/object:Gem::Requirement
283
+ none: false
284
+ requirements:
285
+ - - ! '>='
286
+ - !ruby/object:Gem::Version
287
+ version: '0'
288
+ requirements: []
289
+ rubyforge_project:
290
+ rubygems_version: 1.8.24
291
+ signing_key:
292
+ specification_version: 3
293
+ summary: SMTP Proxy for Amazon Simple Email Service with bounce and complaints support
294
+ test_files: []