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 +7 -0
- data/.gitignore +22 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +159 -0
- data/Rakefile +2 -0
- data/app/controllers/mailkick/subscriptions_controller.rb +67 -0
- data/app/helpers/mailkick/url_helper.rb +15 -0
- data/app/models/mailkick/opt_out.rb +7 -0
- data/app/views/mailkick/subscriptions/show.html.erb +36 -0
- data/config/routes.rb +10 -0
- data/lib/generators/mailkick/install_generator.rb +30 -0
- data/lib/generators/mailkick/templates/install.rb +15 -0
- data/lib/generators/mailkick/views_generator.rb +14 -0
- data/lib/mailkick/engine.rb +13 -0
- data/lib/mailkick/mailer.rb +19 -0
- data/lib/mailkick/model.rb +40 -0
- data/lib/mailkick/processor.rb +23 -0
- data/lib/mailkick/service/mandrill.rb +25 -0
- data/lib/mailkick/service/sendgrid.rb +46 -0
- data/lib/mailkick/service.rb +24 -0
- data/lib/mailkick/version.rb +3 -0
- data/lib/mailkick.rb +32 -0
- data/mailkick.gemspec +23 -0
- metadata +96 -0
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
data/Gemfile
ADDED
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,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,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,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
|
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: []
|