mailkick 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 716fa3a1fdcc00415b50a343d0d38eb434bece3f
4
+ data.tar.gz: 35649c58e33998450e8494620619720aa02b9448
5
+ SHA512:
6
+ metadata.gz: fe3729d2ec69f27b1216ad302770560b826363b23ded849b694694e321e33938cea3d9bff5926d47b488b891fe19af1e53ffc3becfdcf360c65d6877475c7555
7
+ data.tar.gz: 1ba653ab1c80211110aa177179f16e020a01361c2c4ec48fbb7e7399d303d5165de8fe38628aee2181c585d98359407d6499796644ceb998c543ca62e4093e10
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.0.1
2
+
3
+ - First release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mailkick.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Andrew Kane
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # Mailkick
2
+
3
+ :bullettrain_side: Email subscriptions made easy
4
+
5
+ - Add one-click unsubscribe links to your emails
6
+ - Fetch bounces and spam reports from your email service
7
+
8
+ Gracefully handles email address changes
9
+
10
+ :postbox: Check out [Ahoy Email](https://github.com/ankane/ahoy_email) for analytics
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application’s Gemfile:
15
+
16
+ ```ruby
17
+ gem 'mailkick'
18
+ ```
19
+
20
+ And run the generator. This creates a model to store opt-outs.
21
+
22
+ ```sh
23
+ rails generate mailkick:install
24
+ rake db:migrate
25
+ ```
26
+
27
+ ## How It Works
28
+
29
+ Add an unsubscribe link to your emails.
30
+
31
+ #### Text
32
+
33
+ ```erb
34
+ Unsubscribe: <%= mailkick_unsubscribe_url %>
35
+ ```
36
+
37
+ #### HTML
38
+
39
+ ```erb
40
+ <%= link_to "Unsubscribe", mailkick_unsubscribe_url %>
41
+ ```
42
+
43
+ When a user unsubscribes, he or she is taken to a mobile-friendly page and given the option to resubscribe.
44
+
45
+ To customize the view, run:
46
+
47
+ ```sh
48
+ rails generate mailkick:views
49
+ ```
50
+
51
+ which copies the view into `app/views/mailkick`.
52
+
53
+ ## Sending Emails
54
+
55
+ Before sending marketing emails, make sure the user has not opted out.
56
+
57
+ Add the following the method to your user model.
58
+
59
+ ```ruby
60
+ class User < ActiveRecord::Base
61
+ mailkick_user
62
+ end
63
+ ```
64
+
65
+ Get all users who have not unsubscribed
66
+
67
+ ```ruby
68
+ User.subscribed
69
+ ```
70
+
71
+ Check one user
72
+
73
+ ```ruby
74
+ user.subscribed?
75
+ ```
76
+
77
+ Unsubscribe
78
+
79
+ ```ruby
80
+ user.unsubscribe
81
+ ```
82
+
83
+ Subscribe
84
+
85
+ ```ruby
86
+ user.subscribe
87
+ ```
88
+
89
+ ## Bounces and Spam Reports
90
+
91
+ Pull bounces, spam reports, and unsubscribes from your email service.
92
+
93
+ ```ruby
94
+ Mailkick.fetch_opt_outs
95
+ ```
96
+
97
+ #### Sendgrid
98
+
99
+ Add the gem
100
+
101
+ ```ruby
102
+ gem 'sendgrid_toolkit'
103
+ ```
104
+
105
+ Be sure `ENV["SENDGRID_USERNAME"]` and `ENV["SENDGRID_PASSWORD"]` are set.
106
+
107
+ #### Mandrill [broken]
108
+
109
+ ```ruby
110
+ gem 'mandrill-api'
111
+ ```
112
+
113
+ Be sure `ENV["MANDRILL_APIKEY"]` is set.
114
+
115
+ #### Other
116
+
117
+ Send a pull request.
118
+
119
+ ### Advanced
120
+
121
+ For more control over the services, set them by hand.
122
+
123
+ ```ruby
124
+ Mailkick.services = [
125
+ Mailkick::Service::Sendgrid.new(api_key: "API_KEY"),
126
+ Mailkick::Service::Mandrill.new(api_key: "API_KEY")
127
+ ]
128
+ ```
129
+
130
+ ## Multiple Lists
131
+
132
+ Coming soon
133
+
134
+ ## Bonus
135
+
136
+ More great gems for email
137
+
138
+ - [Roadie](https://github.com/Mange/roadie) - inline CSS
139
+
140
+ ## Reference
141
+
142
+ Change how the user is determined
143
+
144
+ ```ruby
145
+ Mailkick.user_method = proc {|email| User.where(email: email).first }
146
+ ```
147
+
148
+ ## History
149
+
150
+ View the [changelog](https://github.com/ankane/mailkick/blob/master/CHANGELOG.md)
151
+
152
+ ## Contributing
153
+
154
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
155
+
156
+ - [Report bugs](https://github.com/ankane/mailkick/issues)
157
+ - Fix bugs and [submit pull requests](https://github.com/ankane/mailkick/pulls)
158
+ - Write, clarify, or fix documentation
159
+ - Suggest or add new features
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,67 @@
1
+ module Mailkick
2
+ class SubscriptionsController < ActionController::Base
3
+ before_filter :set_email
4
+
5
+ def show
6
+ end
7
+
8
+ def unsubscribe
9
+ if subscribed?
10
+ Mailkick::OptOut.create! do |o|
11
+ o.email = @email
12
+ o.user = @user
13
+ o.reason = "unsubscribe"
14
+ end
15
+ end
16
+
17
+ redirect_to subscription_path(params[:id])
18
+ end
19
+
20
+ def subscribe
21
+ Mailkick::OptOut.where(email: @email, active: true).each do |opt_out|
22
+ opt_out.active = false
23
+ opt_out.save!
24
+ end
25
+ if @user and @user.respond_to?(:subscribe)
26
+ @user.subscribe
27
+ end
28
+
29
+ redirect_to subscription_path(params[:id])
30
+ end
31
+
32
+ protected
33
+
34
+ def set_email
35
+ verifier = ActiveSupport::MessageVerifier.new(Mailkick.secret_token)
36
+ begin
37
+ @email, user_id, user_type = verifier.verify(params[:id])
38
+ if user_type
39
+ # on the unprobabilistic chance user_type is compromised, not much damage
40
+ @user = user_type.constantize.find(user_id)
41
+ end
42
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
43
+ render text: "Subscription not found", status: :bad_request
44
+ end
45
+ end
46
+
47
+ def subscribed?
48
+ if @user and @user.respond_to?(:subscribed?)
49
+ @user.subscribed?
50
+ else
51
+ Mailkick::OptOut.where(email: @email, active: true).empty?
52
+ end
53
+ end
54
+ helper_method :subscribed?
55
+
56
+ def subscribe_url
57
+ subscribe_subscription_path(params[:id])
58
+ end
59
+ helper_method :subscribe_url
60
+
61
+ def unsubscribe_url
62
+ unsubscribe_subscription_path(params[:id])
63
+ end
64
+ helper_method :unsubscribe_url
65
+
66
+ end
67
+ end
@@ -0,0 +1,15 @@
1
+ module Mailkick
2
+ module UrlHelper
3
+
4
+ def mailkick_unsubscribe_url
5
+ Mailkick::Engine.routes.url_helpers.url_for(
6
+ Rails.application.config.action_mailer.default_url_options.merge(
7
+ controller: "mailkick/subscriptions",
8
+ action: "unsubscribe",
9
+ id: "{{MAILKICK_TOKEN}}"
10
+ )
11
+ )
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Mailkick
2
+ class OptOut < ActiveRecord::Base
3
+ self.table_name = "mailkick_opt_outs"
4
+
5
+ belongs_to :user, polymorphic: true
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Subscriptions</title>
5
+
6
+ <style>
7
+ body {
8
+ padding: 20px;
9
+ font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
10
+ font-size: 14px;
11
+ line-height: 1.4;
12
+ }
13
+
14
+ a {
15
+ color: blue;
16
+ }
17
+
18
+ .container {
19
+ max-width: 500px;
20
+ margin-left: auto;
21
+ margin-right: auto;
22
+ }
23
+ </style>
24
+ </head>
25
+ <body>
26
+ <div class="container">
27
+ <% if subscribed? %>
28
+ <p>You are subscribed.</p>
29
+ <p><%= link_to "Unsubscribe", unsubscribe_url %></p>
30
+ <% else %>
31
+ <p>You are unsubscribed.</p>
32
+ <p><%= link_to "Resubscribe", subscribe_url %></p>
33
+ <% end %>
34
+ </div>
35
+ </body>
36
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,10 @@
1
+ Rails.application.routes.draw do
2
+ mount Mailkick::Engine => "/mailkick"
3
+ end
4
+
5
+ Mailkick::Engine.routes.draw do
6
+ resources :subscriptions, only: [:show] do
7
+ get :unsubscribe, on: :member
8
+ get :subscribe, on: :member
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ # taken from https://github.com/collectiveidea/audited/blob/master/lib/generators/audited/install_generator.rb
2
+ require "rails/generators"
3
+ require "rails/generators/migration"
4
+ require "active_record"
5
+ require "rails/generators/active_record"
6
+
7
+ module Mailkick
8
+ module Generators
9
+ class InstallGenerator < Rails::Generators::Base
10
+ include Rails::Generators::Migration
11
+
12
+ source_root File.expand_path("../templates", __FILE__)
13
+
14
+ # Implement the required interface for Rails::Generators::Migration.
15
+ def self.next_migration_number(dirname) #:nodoc:
16
+ next_migration_number = current_migration_number(dirname) + 1
17
+ if ActiveRecord::Base.timestamped_migrations
18
+ [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
19
+ else
20
+ "%.3d" % next_migration_number
21
+ end
22
+ end
23
+
24
+ def copy_migration
25
+ migration_template "install.rb", "db/migrate/install_mailkick.rb"
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def change
3
+ create_table :mailkick_opt_outs do |t|
4
+ t.string :email
5
+ t.integer :user_id
6
+ t.string :user_type
7
+ t.boolean :active, null: false, default: true
8
+ t.string :reason
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :mailkick_opt_outs, :email
13
+ add_index :mailkick_opt_outs, [:user_id, :user_type]
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ require "rails/generators"
2
+
3
+ module Mailkick
4
+ module Generators
5
+ class ViewsGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("../../../../app/views", __FILE__)
7
+
8
+ def copy_initializer_file
9
+ directory "mailkick", "app/views/mailkick"
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Mailkick
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Mailkick
4
+
5
+ initializer "mailkick" do |app|
6
+ Mailkick.discover_services
7
+ Mailkick.secret_token = app.config.try(:secret_key_base) || app.config.try(:secret_token)
8
+ ActiveSupport.on_load :action_mailer do
9
+ helper Mailkick::UrlHelper
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module Mailkick
2
+ module Mailer
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ alias_method_chain :mail, :mailkick
7
+ end
8
+ end
9
+
10
+ def mail_with_mailkick(headers = {}, &block)
11
+ message = mail_without_mailkick(headers, &block)
12
+
13
+ Mailkick::Processor.new(message).process
14
+
15
+ message
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ module Mailkick
2
+ module Model
3
+
4
+ def mailkick_user(options = {})
5
+ class_eval do
6
+ scope :subscribed, proc{ joins(sanitize_sql_array(["LEFT JOIN mailkick_opt_outs ON #{table_name}.email = mailkick_opt_outs.email OR (#{table_name}.id = mailkick_opt_outs.user_id AND mailkick_opt_outs.user_type = ?)", name])).where("active != ?", true) }
7
+
8
+ def opt_outs
9
+ Mailkick::OptOut.where("email = ? OR (user_id = ? AND user_type = ?)", email, id, self.class.name)
10
+ end
11
+
12
+ def subscribed?
13
+ opt_outs.where(active: true).empty?
14
+ end
15
+
16
+ def subscribe
17
+ opt_outs.where(active: true).each do |opt_out|
18
+ opt_out.active = false
19
+ opt_out.save!
20
+ end
21
+ true
22
+ end
23
+
24
+ def unsubscribe
25
+ if subscribed?
26
+ OptOut.create! do |o|
27
+ o.email = email
28
+ o.user = self
29
+ o.reason = "unsubscribe"
30
+ o.save!
31
+ end
32
+ end
33
+ true
34
+ end
35
+
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,23 @@
1
+ module Mailkick
2
+ class Processor
3
+ attr_reader :message
4
+
5
+ def initialize(message)
6
+ @message = message
7
+ end
8
+
9
+ def process
10
+ email = message.to.first
11
+ user = Mailkick.user_method.call(email) if Mailkick.user_method
12
+
13
+ verifier = ActiveSupport::MessageVerifier.new(Mailkick.secret_token)
14
+ token = verifier.generate([email, user.try(:id), user.try(:class).try(:name)])
15
+
16
+ parts = message.parts.any? ? message.parts : [message]
17
+ parts.each do |part|
18
+ part.body.raw_source.gsub!(/%7B%7BMAILKICK_TOKEN%7D%7D/, token)
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # https://mandrillapp.com/api/docs/index.ruby.html
2
+
3
+ module Mailkick
4
+ class Service
5
+ class Mandrill < Mailkick::Service
6
+
7
+ def initialize(options = {})
8
+ require "mandrill"
9
+ @mandrill = ::Mandrill::API.new(options[:api_key] || ENV["MANDRILL_APIKEY"])
10
+ end
11
+
12
+ def opt_outs
13
+ # @mandrill.rejects.list.map do |record|
14
+ # record
15
+ # end
16
+ []
17
+ end
18
+
19
+ def self.discoverable?
20
+ !!(defined?(::Mandrill::API) && ENV["MANDRILL_APIKEY"])
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,46 @@
1
+ # https://github.com/freerobby/sendgrid_toolkit
2
+
3
+ module Mailkick
4
+ class Service
5
+ class Sendgrid < Mailkick::Service
6
+
7
+ def initialize(options = {})
8
+ @api_user = options[:api_user] || ENV["SENDGRID_USERNAME"]
9
+ @api_key = options[:api_key] || ENV["SENDGRID_PASSWORD"]
10
+ end
11
+
12
+ def opt_outs
13
+ unsubscribes + spam_reports + bounces
14
+ end
15
+
16
+ def unsubscribes
17
+ fetch(::SendgridToolkit::Unsubscribes, "unsubscribe")
18
+ end
19
+
20
+ def spam_reports
21
+ fetch(::SendgridToolkit::SpamReports, "spam")
22
+ end
23
+
24
+ def bounces
25
+ fetch(::SendgridToolkit::Bounces, "bounce")
26
+ end
27
+
28
+ def self.discoverable?
29
+ !!(defined?(::SendgridToolkit) && ENV["SENDGRID_USERNAME"] && ENV["SENDGRID_PASSWORD"])
30
+ end
31
+
32
+ protected
33
+
34
+ def fetch(klass, reason)
35
+ klass.new(@api_user, @api_key).retrieve_with_timestamps.map do |record|
36
+ {
37
+ email: record["email"],
38
+ time: record["created"],
39
+ reason: reason
40
+ }
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,24 @@
1
+ module Mailkick
2
+ class Service
3
+
4
+ def fetch_opt_outs
5
+ opt_outs.each do |api_data|
6
+ email = api_data[:email]
7
+ time = api_data[:time]
8
+
9
+ opt_out = Mailkick::OptOut.where(email: email).order("updated_at desc").first
10
+ if !opt_out or (time > opt_out.updated_at and !opt_out.active)
11
+ Mailkick::OptOut.create! do |o|
12
+ o.email = email
13
+ o.user = Mailkick.user_method if Mailkick.user_method.call(email)
14
+ o.reason = api_data[:reason]
15
+ o.created_at = time
16
+ o.updated_at = time
17
+ end
18
+ end
19
+ end
20
+ true
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module Mailkick
2
+ VERSION = "0.0.1"
3
+ end
data/lib/mailkick.rb ADDED
@@ -0,0 +1,32 @@
1
+ require "mailkick/version"
2
+ require "mailkick/engine"
3
+ require "mailkick/processor"
4
+ require "mailkick/mailer"
5
+ require "mailkick/model"
6
+ require "mailkick/service"
7
+ require "mailkick/service/mandrill"
8
+ require "mailkick/service/sendgrid"
9
+
10
+ module Mailkick
11
+ mattr_accessor :services, :user_method, :secret_token
12
+ self.services = []
13
+ self.user_method = proc{|email| User.where(email: email).first rescue nil }
14
+
15
+ def self.fetch_opt_outs
16
+ services.each do |service|
17
+ service.fetch_opt_outs
18
+ end
19
+ end
20
+
21
+ def self.discover_services
22
+ Service.subclasses.each do |service|
23
+ if service.discoverable?
24
+ services << service.new
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ ActionMailer::Base.send :include, Mailkick::Mailer
32
+ ActiveRecord::Base.send(:extend, Mailkick::Model) if defined?(ActiveRecord)
data/mailkick.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mailkick/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mailkick"
8
+ spec.version = Mailkick::VERSION
9
+ spec.authors = ["Andrew Kane"]
10
+ spec.email = ["andrew@chartkick.com"]
11
+ spec.summary = %q{Email subscriptions made easy}
12
+ spec.description = %q{Email subscriptions made easy}
13
+ spec.homepage = "https://github.com/ankane/mailkick"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mailkick
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kane
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Email subscriptions made easy
42
+ email:
43
+ - andrew@chartkick.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - CHANGELOG.md
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - app/controllers/mailkick/subscriptions_controller.rb
55
+ - app/helpers/mailkick/url_helper.rb
56
+ - app/models/mailkick/opt_out.rb
57
+ - app/views/mailkick/subscriptions/show.html.erb
58
+ - config/routes.rb
59
+ - lib/generators/mailkick/install_generator.rb
60
+ - lib/generators/mailkick/templates/install.rb
61
+ - lib/generators/mailkick/views_generator.rb
62
+ - lib/mailkick.rb
63
+ - lib/mailkick/engine.rb
64
+ - lib/mailkick/mailer.rb
65
+ - lib/mailkick/model.rb
66
+ - lib/mailkick/processor.rb
67
+ - lib/mailkick/service.rb
68
+ - lib/mailkick/service/mandrill.rb
69
+ - lib/mailkick/service/sendgrid.rb
70
+ - lib/mailkick/version.rb
71
+ - mailkick.gemspec
72
+ homepage: https://github.com/ankane/mailkick
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.2.2
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Email subscriptions made easy
96
+ test_files: []