mailkick 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []