aws_ses_newsletters 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f8cf510cd0e29876670ef28739b0222642ab5e6c
4
+ data.tar.gz: 07b6efac74634925389d304a05bd2cb5302976c3
5
+ SHA512:
6
+ metadata.gz: 1e44ff3b4a92e6b934c45a3ac8780a83cca6f7d50d5e613ae097383878f7d46a16511ed3b1f0dc15a576a06b344c691b9a0c2679adb205ce90a7c348d2bf0b6a
7
+ data.tar.gz: 0a0167cb316ebdd28e19832cd077cdec0a3b63cc60efd8f96a505f4a2365cfafe6401ac3ca9fc44781a8bcd17b6d370c58bb591d1047b42dd8e6ad58e88c7847
@@ -0,0 +1,20 @@
1
+ Copyright 2016 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.
@@ -0,0 +1,118 @@
1
+ = AwsSesNewsletters
2
+
3
+ This project rocks and uses MIT-LICENSE.
4
+
5
+ {<img src="https://codeship.com/projects/1ddc9a90-e61d-0133-54a1-0e12c0a498c1/status?branch=master" alt="Status?branch=master" />}[https://codeship.com/projects/146708]
6
+
7
+ == What is AwsSesNewsletters
8
+
9
+ AwsSesNewsletters is a Rails plugin or, using the new nomenclature a gemified plugin, that allows you to
10
+ very easily construct newsletters that are going to be sent with Amazon SES. In other words, it is a wrapper
11
+ over aws-ses that provides some basic functionality to construct the emails and a common workflow to send
12
+ the emails.
13
+
14
+ == Dependencies
15
+
16
+ 1. https://github.com/drewblas/aws-ses
17
+ 2. https://github.com/premailer/premailer
18
+ 3. https://github.com/mperham/sidekiq
19
+
20
+ == How to install
21
+
22
+ === Install the gem, either as
23
+ <tt>gem install aws_ses_newsletters</tt>
24
+
25
+ or adding
26
+
27
+ <tt>gem 'aws_ses_newsletters'</tt> to your Gemfile and running <tt>bundle install</tt>
28
+
29
+ === Install the needed migrations, to generate the table for Newsletters & Emails Responses
30
+ <tt>rake aws_ses_newsletters:install:migrations</tt>
31
+
32
+ This will add 2 migration, that you need to run:
33
+
34
+ <tt>rake db:migrate</tt>
35
+
36
+ === Mount the engine in the path of your preference. In <b>routes.rb</b>
37
+ <tt>mount AwsSesNewsletters::Engine, at: "/newsletters"</tt>
38
+
39
+ This will add 2 new routes to your application:
40
+ a. POST newsletters/email_responses/bounce
41
+ b. POST newsletters/email_responses/complaint
42
+
43
+ These are the routes you need to configure in *SNS* to receive notifications for bounces and complaints respectively.
44
+
45
+ === Add your amazon keys
46
+ a. SES_ACCESS_KEY_ID
47
+ b. SES_SECRET_ACCESS_KEY
48
+
49
+ In heroku, you need to set them as:
50
+
51
+ heroku config:set SES_ACCESS_KEY_ID=<you_access_keid>
52
+ heroku config:set SES_SECRET_ACCESS_KEY=<you_secret_access_key>
53
+
54
+ == Usage
55
+
56
+ To create a Newsletter, you need to inherit from AwsSesNewsletters::NewslettersSender and implement 2 methods:
57
+ 1. create_newsletter: which should create & return a AwsSesNewsletters::Newsletter
58
+ 2. get_recipients: which should iterate over your recipients and yield with them. For example:
59
+
60
+ # def get_recipients
61
+ # recipient = Recipient.new('fzuppa@10pines.com', 'Federico', 'Zuppa')
62
+ # yield recipient
63
+ # end
64
+
65
+ After creating this class, you will usually execute it asynchronously using sidekiq:
66
+
67
+ <tt>YourClass.perform_async</tt>
68
+
69
+ To test it in the rails console, of course that you can do *YourClass.new.perform*
70
+
71
+ == A few important use cases
72
+
73
+ There is a demo project that shows the most important use cases I had inmind when building this plugin: https://github.com/10Pines/aws_ses_newsletters_demo
74
+
75
+ Here's a quick explanation of them:
76
+
77
+ === Create the html using ERB
78
+
79
+ There is a helper class to do this called AwsSesNewsletters::HtmlBuilder. It takes 2 parameters
80
+
81
+ a. The template erb that will be used to construct the html
82
+ b. A hash with variables needed to construct the html
83
+
84
+ For example, take a look at NewslettersWithVariableReplacementsSender in the demo:
85
+
86
+ html_body: AwsSesNewsletters::HtmlBuilder.new(
87
+ "#{::Rails.root}/app/views/newsletter_with_instance_variables.html.erb",
88
+ {promotions: [OpenStruct.new(name: '10% off this week')]}
89
+ ).build
90
+
91
+ === Replace strings after the html is built
92
+ You need to do this to put the recipient's email for each mail or an unsubscription token (those were the 2 things I needed)
93
+
94
+ If you want to perform such replacement, just override <tt>do_custom_replacements_for(email, recipient)</tt>
95
+
96
+ For example:
97
+ def do_custom_replacements_for(mail, recipient)
98
+ mail.html_part.body = mail.html_part.body.raw_source.gsub('recipient_email', recipient.email)
99
+ end
100
+
101
+ === Send inline attachments
102
+ This is discouraged. I found out after building the gem and that is why I left it (in case you need it)
103
+
104
+ To do such thing, simply override <tt>get_images</tt> returning a hash of images.
105
+ def get_images
106
+ images = {}
107
+ images["logo10pines"] = File.read(Rails.root.join('public/mailresources/logo-fullcolor_150px.png'))
108
+ images
109
+ end
110
+
111
+ In the html, simply put the id that you used in the hash.
112
+
113
+ <img src="logo10pines" %>
114
+
115
+ Please look at the demo example that it will be much easier to understand!
116
+
117
+ ==== Author: Federico Zuppa
118
+ mailto:fzuppa@10pines.com
@@ -0,0 +1,23 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'AwsSesNewsletters'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/test_app/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,5 @@
1
+ module AwsSesNewsletters
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -0,0 +1,71 @@
1
+ require_dependency "aws_ses_newsletters/application_controller"
2
+
3
+ module AwsSesNewsletters
4
+ class EmailResponsesController < ApplicationController
5
+ skip_before_action :verify_authenticity_token
6
+ before_action :log_incoming_message
7
+
8
+ def bounce
9
+ # TODO: get a certificate and make this secure
10
+ # return render json: {} unless aws_message.authentic?
11
+
12
+ if type != 'Bounce'
13
+ puts "Not a bounce - exiting"
14
+ return render json: {}
15
+ end
16
+
17
+ bounce = message['bounce']
18
+ bouncerecps = bounce['bouncedRecipients']
19
+ bouncerecps.each do |recp|
20
+ email = recp['emailAddress']
21
+ extra_info = "status: #{recp['status']}, action: #{recp['action']}, diagnosticCode: #{recp['diagnosticCode']}"
22
+
23
+ EmailResponse.create ({ email: email, response_type: 'bounce', extra_info: extra_info})
24
+ end
25
+
26
+ render json: {}
27
+ end
28
+
29
+ def complaint
30
+ #return render json: {} unless aws_message.authentic?
31
+
32
+ if type != 'Complaint'
33
+ Rails.logger.info "Not a complaint - exiting"
34
+ return render json: {}
35
+ end
36
+
37
+ complaint = message['complaint']
38
+ recipients = complaint['complainedRecipients']
39
+ recipients.each do |recp|
40
+ email = recp['emailAddress']
41
+ extra_info = "complaintFeedbackType: #{complaint['complaintFeedbackType']}"
42
+ EmailResponse.create ( { email: email, response_type: 'complaint', extra_info: extra_info } )
43
+ end
44
+
45
+ render json: {}
46
+ end
47
+
48
+ private
49
+
50
+ # def aws_message
51
+ # @aws_message ||= AWS::SNS::Message.new request.raw_post
52
+ # end
53
+
54
+ def email_response_params
55
+ params.require(:email_response).permit(:email, :extra_info, :response_type)
56
+ end
57
+
58
+ # Weirdly, AWS double encodes the JSON.
59
+ def message
60
+ @message ||= JSON.parse JSON.parse(request.raw_post)['Message']
61
+ end
62
+
63
+ def type
64
+ message['notificationType']
65
+ end
66
+
67
+ def log_incoming_message
68
+ Rails.logger.info request.raw_post
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,4 @@
1
+ module AwsSesNewsletters
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module AwsSesNewsletters
2
+ class EmailResponse < ActiveRecord::Base
3
+ enum response_type: [ :bounce, :complaint ]
4
+
5
+ validates_presence_of :email
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ module AwsSesNewsletters
2
+ class Newsletter < ActiveRecord::Base
3
+ end
4
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>AwsSesNewsletters</title>
5
+ <%= stylesheet_link_tag "aws_ses_newsletters/application", media: "all" %>
6
+ <%= javascript_include_tag "aws_ses_newsletters/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,107 @@
1
+ require 'aws_ses_newsletters/mail_builder'
2
+
3
+ module AwsSesNewsletters
4
+ # Inherit from this class to create a Newsletter and override *create_newsletter* & *get_recipients*
5
+ # It is a Sidekiq::Worker, so the method that will be executed is *perform*, which creates a AwsSesNewsletters::Newsletter
6
+ # and sends the newsletter to all defined recipients.
7
+ class NewslettersSender
8
+ include ::Sidekiq::Worker
9
+ attr_accessor :newsletter
10
+
11
+ SES = AWS::SES::Base.new(access_key_id: ENV['SES_ACCESS_KEY_ID'],
12
+ secret_access_key: ENV['SES_SECRET_ACCESS_KEY'])
13
+
14
+ # this method is the one that is executed when starting a sidekiq process
15
+ def perform
16
+ @newsletter = create_newsletter
17
+ send_emails
18
+ end
19
+
20
+ # Send a preview email
21
+ def preview_to(recipient)
22
+ @newsletter = build_newsletter
23
+ mail = build_mail
24
+ mail.to = recipient.email
25
+ replace_and_send_mail_safely(mail, recipient)
26
+ end
27
+ protected
28
+
29
+ # Iterate over recipients and sends emails
30
+ def send_emails
31
+ mail = build_mail
32
+ get_recipients do |recipient|
33
+ next if EmailResponse.exists?(email: recipient.email) # bounces & complaints
34
+ mail.to = recipient.email
35
+ replace_and_send_mail_safely(mail, recipient)
36
+ end
37
+ end
38
+
39
+ # Override this method to create a AwsSesNewsletters::Newsletter
40
+ # ==== Example
41
+ # AwsSesNewsletter::Newsletter.create(from: 'you@example.com', subject: 'newsletter subject', html_body: '<p>Newsletter here</p>')
42
+ def create_newsletter
43
+ fail NotImplementedError
44
+ end
45
+
46
+ # Override this method to build a AwsSesNewsletters::Newsletter to preview
47
+ # ==== Example
48
+ # AwsSesNewsletter::Newsletter.new(from: 'you@example.com', subject: 'newsletter subject', html_body: '<p>Newsletter here</p>')
49
+ def build_newsletter
50
+ fail NotImplementedError
51
+ end
52
+
53
+ # Override this method to get the recipients, yielding for every recipient you want to send an email to
54
+ # It is recommended to batch if there are many recipients
55
+ # ==== Example
56
+ # User.where(wants_newsletter: true).find_in_batches(batch_size: 100) do |group|
57
+ # group.each do |recipient|
58
+ # yield recipient
59
+ # end
60
+ # end
61
+ def get_recipients
62
+ fail NotImplementedError
63
+ end
64
+
65
+ # override this method if you want to inline images
66
+ # return a Hash where the key is the key used in the template and the value is the file
67
+ def get_images
68
+ {}
69
+ end
70
+
71
+ # Perform custom replacements and send the email without throwing any exception
72
+ def replace_and_send_mail_safely(mail, recipient)
73
+ html_body = mail.html_part.body.raw_source
74
+ do_custom_replacements_for(mail, recipient)
75
+ send_raw_email_safely(mail)
76
+ mail.html_part.body = html_body
77
+ end
78
+
79
+ # calls aws-ses *send_raw_email* with a +mail+
80
+ def send_raw_email_safely(mail)
81
+ begin
82
+ SES.send_raw_email(mail)
83
+ rescue StandardError => e
84
+ Rails.logger.info e.message
85
+ end
86
+ end
87
+
88
+ # Override this method to perform a replacement for a particular email
89
+ # Usually you will want to override the body
90
+ # ==== Example
91
+ # mail.html_part.body = mail.html_part.body.raw_source.gsub('recipient_email', recipient.email)
92
+ def do_custom_replacements_for(mail, recipient)
93
+ end
94
+
95
+ private
96
+
97
+ # Builds a Mail
98
+ def build_mail
99
+ AwsSesNewsletters::MailBuilder.new(
100
+ from: newsletter.from,
101
+ subject: newsletter.subject,
102
+ html_body: newsletter.html_body,
103
+ images: get_images,
104
+ ).build
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,5 @@
1
+ AwsSesNewsletters::Engine.routes.draw do
2
+ # SNS
3
+ post 'email_responses/bounce' => 'email_responses#bounce'
4
+ post 'email_responses/complaint' => 'email_responses#complaint'
5
+ end
@@ -0,0 +1,12 @@
1
+ class CreateAwsSesNewslettersNewsletters < ActiveRecord::Migration
2
+ def change
3
+ create_table :aws_ses_newsletters_newsletters do |t|
4
+ t.string :from
5
+ t.string :subject
6
+ t.text :html_body
7
+ t.text :text_body
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ class CreateAwsSesNewslettersEmailResponses < ActiveRecord::Migration
2
+ def change
3
+ create_table :aws_ses_newsletters_email_responses do |t|
4
+ t.string :email
5
+ t.text :extra_info
6
+ t.integer :response_type
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ require "aws_ses_newsletters/engine"
2
+ require "aws/ses"
3
+
4
+ module AwsSesNewsletters
5
+ end
@@ -0,0 +1,10 @@
1
+ module AwsSesNewsletters
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace AwsSesNewsletters
4
+
5
+ config.generators do |g|
6
+ g.test_framework :rspec
7
+ g.fixture_replacement :factory_girl, :dir => 'spec/factories'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,29 @@
1
+ require 'premailer'
2
+
3
+ module AwsSesNewsletters
4
+ # Helper that allows you to build html from erb templates, inlines css as needed in emails
5
+ class HtmlBuilder
6
+ include Rails.application.routes.url_helpers
7
+ include ActionView::Helpers
8
+ include ActionView::Rendering
9
+
10
+ attr_accessor :template_name
11
+
12
+ def initialize(template_name, instance_variables_hash = nil)
13
+ @template_name = template_name
14
+ instance_variables_hash.each { |name, value| instance_variable_set("@#{name}", value) } if instance_variables_hash.present?
15
+ end
16
+
17
+ def build
18
+ template = File.read(template_name)
19
+ renderer = ERB.new(template)
20
+ html_string = renderer.result(get_binding)
21
+ inline_html_string = ::Premailer.new(html_string, warn_level: Premailer::Warnings::SAFE, with_html_string: true).to_inline_css
22
+ return inline_html_string.gsub(/\n/, "")
23
+ end
24
+
25
+ def get_binding
26
+ binding()
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ module AwsSesNewsletters
2
+ # Helper to build a Mail
3
+ class MailBuilder
4
+ attr_accessor :from, :to, :subject, :html_body, :images
5
+
6
+ def initialize(from:, subject:, html_body:, images: [])
7
+ @from = from
8
+ @subject = subject
9
+ @html_body = html_body
10
+ @images = images
11
+ end
12
+
13
+ def build
14
+ from = self.from
15
+ subject = self.subject
16
+ html_body = self.html_body
17
+ images = self.images
18
+
19
+ mail = Mail.new do
20
+ from from
21
+ subject subject
22
+ html = html_body
23
+
24
+ # attachments
25
+ images.each do |key, value|
26
+ attachments[key] = value
27
+ end
28
+ attachments.each do |attachment|
29
+ (html = html.sub(attachment.filename, "cid:#{attachment.cid}")) if (html =~ /#{attachment.filename}/)
30
+ end
31
+
32
+ html_part do
33
+ body html; content_type 'text/html; charset=UTF-8'
34
+ end
35
+ end
36
+ mail.raise_delivery_errors = true
37
+ mail
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module AwsSesNewsletters
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :aws_ses_newsletters do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws_ses_newsletters
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Federico Zuppa
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-ses
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sidekiq
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: nokogiri
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: premailer
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: factory_girl_rails
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Framework that uses aws-ses to send newsletters
126
+ email:
127
+ - fzuppa@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - MIT-LICENSE
133
+ - README.rdoc
134
+ - Rakefile
135
+ - app/assets/javascripts/aws_ses_newsletters/application.js
136
+ - app/assets/stylesheets/aws_ses_newsletters/application.css
137
+ - app/controllers/aws_ses_newsletters/application_controller.rb
138
+ - app/controllers/aws_ses_newsletters/email_responses_controller.rb
139
+ - app/helpers/aws_ses_newsletters/application_helper.rb
140
+ - app/models/aws_ses_newsletters/email_response.rb
141
+ - app/models/aws_ses_newsletters/newsletter.rb
142
+ - app/views/layouts/aws_ses_newsletters/application.html.erb
143
+ - app/workers/aws_ses_newsletters/newsletters_sender.rb
144
+ - config/routes.rb
145
+ - db/migrate/20160410174449_create_aws_ses_newsletters_newsletters.rb
146
+ - db/migrate/20160411205706_create_aws_ses_newsletters_email_responses.rb
147
+ - lib/aws_ses_newsletters.rb
148
+ - lib/aws_ses_newsletters/engine.rb
149
+ - lib/aws_ses_newsletters/html_builder.rb
150
+ - lib/aws_ses_newsletters/mail_builder.rb
151
+ - lib/aws_ses_newsletters/version.rb
152
+ - lib/tasks/aws_ses_newsletters_tasks.rake
153
+ homepage: https://github.com/10pines/aws_ses_newsletters
154
+ licenses:
155
+ - MIT
156
+ metadata: {}
157
+ post_install_message:
158
+ rdoc_options: []
159
+ require_paths:
160
+ - lib
161
+ required_ruby_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ required_rubygems_version: !ruby/object:Gem::Requirement
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ version: '0'
171
+ requirements: []
172
+ rubyforge_project:
173
+ rubygems_version: 2.4.6
174
+ signing_key:
175
+ specification_version: 4
176
+ summary: Easy newsletters with Amazon SES
177
+ test_files: []