mailkick 0.3.0 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -17
- data/LICENSE.txt +1 -1
- data/README.md +64 -16
- data/app/controllers/mailkick/subscriptions_controller.rb +1 -5
- data/app/models/mailkick/opt_out.rb +1 -1
- data/lib/generators/mailkick/install_generator.rb +3 -20
- data/lib/generators/mailkick/templates/{install.rb → install.rb.tt} +0 -0
- data/lib/mailkick.rb +30 -13
- data/lib/mailkick/engine.rb +1 -5
- data/lib/mailkick/model.rb +10 -9
- data/lib/mailkick/service/aws_ses.rb +47 -0
- data/lib/mailkick/service/postmark.rb +41 -0
- data/lib/mailkick/service/sendgrid.rb +4 -1
- data/lib/mailkick/service/sendgrid_v2.rb +52 -0
- data/{app/helpers → lib}/mailkick/url_helper.rb +4 -3
- data/lib/mailkick/version.rb +1 -1
- metadata +32 -44
- data/.gitignore +0 -24
- data/.travis.yml +0 -16
- data/Gemfile +0 -6
- data/Rakefile +0 -9
- data/mailkick.gemspec +0 -32
- data/test/gemfiles/actionmailer42.gemfile +0 -6
- data/test/gemfiles/actionmailer50.gemfile +0 -6
- data/test/gemfiles/actionmailer51.gemfile +0 -6
- data/test/internal/app/mailers/user_mailer.rb +0 -7
- data/test/internal/app/models/user.rb +0 -3
- data/test/internal/app/views/user_mailer/welcome.html.erb +0 -1
- data/test/internal/app/views/user_mailer/welcome.text.erb +0 -1
- data/test/internal/config/database.yml +0 -3
- data/test/internal/db/schema.rb +0 -16
- data/test/mailkick_test.rb +0 -29
- data/test/test_helper.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a3e1a782d63a1ad223871a87aab2875c73c1a7860f614801184d005113d4d82
|
4
|
+
data.tar.gz: 802fdb1f89e3ca6c25761bb61fd085b1936336ea5fd6bc6a020a1b79a21859c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e93ee8b3bcf05fa1536141e2f9ef639e30efd236eb9d280bb88bc7e234f6a6aa9a246eb91bd6aee188c74226e11cf0cdcb67e636ffafeb76f525f89096ec5eda
|
7
|
+
data.tar.gz: 23441f577b0f294a46f47e61cc66c994a319d1e3d89ff310bd26d997c5b6bdbd2270318fda74de96e588f7da58691c63d3c188853be382a41a3608803034435d
|
data/CHANGELOG.md
CHANGED
@@ -1,73 +1,98 @@
|
|
1
|
-
## 0.3
|
1
|
+
## 0.4.3 (2020-11-01)
|
2
|
+
|
3
|
+
- Added support for AWS SES
|
4
|
+
|
5
|
+
## 0.4.2 (2020-04-06)
|
6
|
+
|
7
|
+
- Added support for official SendGrid gem
|
8
|
+
- Fixed deprecation warning
|
9
|
+
|
10
|
+
## 0.4.1 (2019-10-27)
|
11
|
+
|
12
|
+
- Added Postmark support
|
13
|
+
|
14
|
+
## 0.4.0 (2019-07-15)
|
15
|
+
|
16
|
+
- Fixed error with model methods and `email_key` option
|
17
|
+
- Fixed bug with `opted_out` scope
|
18
|
+
- Dropped support for Action Mailer 4.2
|
19
|
+
|
20
|
+
## 0.3.1 (2018-04-21)
|
21
|
+
|
22
|
+
- Fixed `Secret should not be nil` error in Rails 5.2
|
23
|
+
- Gracefully handle missing email
|
24
|
+
- Added `user` option to `mailkick_unsubscribe_url`
|
25
|
+
|
26
|
+
## 0.3.0 (2018-04-20)
|
2
27
|
|
3
28
|
- Improved performance
|
4
29
|
- Fixed `Subscription not found` for Rails 5.2
|
5
30
|
- Use `references` in migration
|
6
31
|
- Use `smtp_settings[:domain]` for Mailgun
|
7
|
-
- Dropped support for
|
32
|
+
- Dropped support for Action Mailer < 4.2
|
8
33
|
|
9
|
-
## 0.2.1
|
34
|
+
## 0.2.1 (2017-10-30)
|
10
35
|
|
11
36
|
- Fixed errors with Rails 5+
|
12
37
|
- Fixed errors with the latest version of Gibbon
|
13
38
|
|
14
|
-
## 0.2.0
|
39
|
+
## 0.2.0 (2017-05-01)
|
15
40
|
|
16
41
|
- Added support for Rails 5.1
|
17
42
|
|
18
|
-
## 0.1.6
|
43
|
+
## 0.1.6 (2017-01-10)
|
19
44
|
|
20
45
|
- Fixed error with frozen strings
|
21
46
|
|
22
|
-
## 0.1.5
|
47
|
+
## 0.1.5 (2016-12-06)
|
23
48
|
|
24
49
|
- Use `safely`
|
25
50
|
- Only discover services if not manually set
|
26
51
|
- Added `mount` option
|
27
52
|
|
28
|
-
## 0.1.4
|
53
|
+
## 0.1.4 (2016-02-20)
|
29
54
|
|
30
55
|
- Use `Module#prepend` instead of `alias_method_chain`
|
31
56
|
|
32
|
-
## 0.1.3
|
57
|
+
## 0.1.3 (2015-06-29)
|
33
58
|
|
34
59
|
- Fixed issue with double escaping tokens
|
35
60
|
|
36
|
-
## 0.1.2
|
61
|
+
## 0.1.2 (2015-06-07)
|
37
62
|
|
38
63
|
- Added support for Mailgun
|
39
64
|
|
40
|
-
## 0.1.1
|
65
|
+
## 0.1.1 (2015-01-31)
|
41
66
|
|
42
67
|
- Fixed tokens with `+` in them
|
43
68
|
|
44
|
-
## 0.1.0
|
69
|
+
## 0.1.0 (2014-08-31)
|
45
70
|
|
46
71
|
- Fixed secret token for Rails 4.1
|
47
72
|
|
48
|
-
## 0.0.6
|
73
|
+
## 0.0.6 (2014-05-09)
|
49
74
|
|
50
75
|
- Rails 3 fix
|
51
76
|
|
52
|
-
## 0.0.5
|
77
|
+
## 0.0.5 (2014-05-05)
|
53
78
|
|
54
79
|
- Fixed bug with subscriptions page
|
55
80
|
|
56
|
-
## 0.0.4
|
81
|
+
## 0.0.4 (2014-05-05)
|
57
82
|
|
58
83
|
- Added `email_key` option to `mailkick_user`
|
59
84
|
|
60
|
-
## 0.0.3
|
85
|
+
## 0.0.3 (2014-05-04)
|
61
86
|
|
62
87
|
- Added support for multiple lists
|
63
88
|
- Changed `mailkick_user` method names - sorry early adopters :(
|
64
89
|
|
65
|
-
## 0.0.2
|
90
|
+
## 0.0.2 (2014-05-04)
|
66
91
|
|
67
92
|
- Added Mailchimp service
|
68
93
|
- Fixed Mandrill service
|
69
94
|
- Added `uniq` to `subscribed` scope
|
70
95
|
|
71
|
-
## 0.0.1
|
96
|
+
## 0.0.1 (2014-05-04)
|
72
97
|
|
73
98
|
- First release
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Mailkick
|
2
2
|
|
3
|
-
Email
|
3
|
+
Email unsubscribes for Rails
|
4
4
|
|
5
5
|
- Add one-click unsubscribe links to your emails
|
6
6
|
- Fetch bounces and spam reports from your email service
|
@@ -89,43 +89,76 @@ Fetch bounces, spam reports, and unsubscribes from your email service.
|
|
89
89
|
Mailkick.fetch_opt_outs
|
90
90
|
```
|
91
91
|
|
92
|
-
|
92
|
+
The following services are supported:
|
93
93
|
|
94
|
-
|
94
|
+
- [AWS SES](#aws-ses)
|
95
|
+
- [Mailchimp](#mailchimp)
|
96
|
+
- [Mailgun](#mailgun)
|
97
|
+
- [Mandrill](#mandrill)
|
98
|
+
- [Postmark](#postmark)
|
99
|
+
- [SendGrid](#sendgrid)
|
95
100
|
|
96
|
-
|
97
|
-
gem 'sendgrid_toolkit'
|
98
|
-
```
|
101
|
+
Will gladly accept pull requests for others.
|
99
102
|
|
100
|
-
|
103
|
+
#### AWS SES
|
101
104
|
|
102
|
-
|
105
|
+
Add the gem
|
103
106
|
|
104
107
|
```ruby
|
105
|
-
gem '
|
108
|
+
gem 'aws-sdk-sesv2'
|
106
109
|
```
|
107
110
|
|
108
|
-
|
111
|
+
And [configure your AWS credentials](https://github.com/aws/aws-sdk-ruby#configuration). Requires `ses:ListSuppressedDestinations` permission.
|
109
112
|
|
110
113
|
#### Mailchimp
|
111
114
|
|
115
|
+
Add the gem
|
116
|
+
|
112
117
|
```ruby
|
113
118
|
gem 'gibbon', '>= 2'
|
114
119
|
```
|
115
120
|
|
116
|
-
|
121
|
+
And set `ENV["MAILCHIMP_API_KEY"]` and `ENV["MAILCHIMP_LIST_ID"]`.
|
117
122
|
|
118
123
|
#### Mailgun
|
119
124
|
|
125
|
+
Add the gem
|
126
|
+
|
120
127
|
```ruby
|
121
128
|
gem 'mailgun-ruby'
|
122
129
|
```
|
123
130
|
|
124
|
-
|
131
|
+
And set `ENV["MAILGUN_API_KEY"]`.
|
125
132
|
|
126
|
-
####
|
133
|
+
#### Mandrill
|
134
|
+
|
135
|
+
Add the gem
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
gem 'mandrill-api'
|
139
|
+
```
|
140
|
+
|
141
|
+
And set `ENV["MANDRILL_APIKEY"]`.
|
127
142
|
|
128
|
-
|
143
|
+
#### Postmark
|
144
|
+
|
145
|
+
Add the gem
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
gem 'postmark'
|
149
|
+
```
|
150
|
+
|
151
|
+
And set `ENV["POSTMARK_API_KEY"]`.
|
152
|
+
|
153
|
+
#### SendGrid
|
154
|
+
|
155
|
+
Add the gem
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
gem 'sendgrid-ruby'
|
159
|
+
```
|
160
|
+
|
161
|
+
And set `ENV["SENDGRID_API_KEY"]`. The API key requires only the `Suppressions` permission.
|
129
162
|
|
130
163
|
### Advanced
|
131
164
|
|
@@ -133,8 +166,8 @@ For more control over services, set them by hand.
|
|
133
166
|
|
134
167
|
```ruby
|
135
168
|
Mailkick.services = [
|
136
|
-
Mailkick::Service::
|
137
|
-
Mailkick::Service::
|
169
|
+
Mailkick::Service::SendGridV2.new(api_key: "API_KEY"),
|
170
|
+
Mailkick::Service::Mailchimp.new(api_key: "API_KEY", list_id: "LIST_ID")
|
138
171
|
]
|
139
172
|
```
|
140
173
|
|
@@ -203,6 +236,12 @@ Resubscribe
|
|
203
236
|
user.opt_in
|
204
237
|
```
|
205
238
|
|
239
|
+
Access the opt-out model directly
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
Mailkick::OptOut.all
|
243
|
+
```
|
244
|
+
|
206
245
|
## History
|
207
246
|
|
208
247
|
View the [changelog](https://github.com/ankane/mailkick/blob/master/CHANGELOG.md)
|
@@ -215,3 +254,12 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
|
|
215
254
|
- Fix bugs and [submit pull requests](https://github.com/ankane/mailkick/pulls)
|
216
255
|
- Write, clarify, or fix documentation
|
217
256
|
- Suggest or add new features
|
257
|
+
|
258
|
+
To get started with development and testing:
|
259
|
+
|
260
|
+
```sh
|
261
|
+
git clone https://github.com/ankane/mailkick.git
|
262
|
+
cd mailkick
|
263
|
+
bundle install
|
264
|
+
bundle exec rake test
|
265
|
+
```
|
@@ -31,11 +31,7 @@ module Mailkick
|
|
31
31
|
list: @list
|
32
32
|
}
|
33
33
|
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
34
|
-
|
35
|
-
render plain: "Subscription not found", status: :bad_request
|
36
|
-
else
|
37
|
-
render text: "Subscription not found", status: :bad_request
|
38
|
-
end
|
34
|
+
render plain: "Subscription not found", status: :bad_request
|
39
35
|
end
|
40
36
|
|
41
37
|
def opted_out?
|
@@ -2,6 +2,6 @@ module Mailkick
|
|
2
2
|
class OptOut < ActiveRecord::Base
|
3
3
|
self.table_name = "mailkick_opt_outs"
|
4
4
|
|
5
|
-
belongs_to :user,
|
5
|
+
belongs_to :user, polymorphic: true, optional: true
|
6
6
|
end
|
7
7
|
end
|
@@ -1,34 +1,17 @@
|
|
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
1
|
require "rails/generators/active_record"
|
6
2
|
|
7
3
|
module Mailkick
|
8
4
|
module Generators
|
9
5
|
class InstallGenerator < Rails::Generators::Base
|
10
|
-
include
|
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
|
6
|
+
include ActiveRecord::Generators::Migration
|
7
|
+
source_root File.join(__dir__, "templates")
|
23
8
|
|
24
9
|
def copy_migration
|
25
10
|
migration_template "install.rb", "db/migrate/install_mailkick.rb", migration_version: migration_version
|
26
11
|
end
|
27
12
|
|
28
13
|
def migration_version
|
29
|
-
|
30
|
-
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
31
|
-
end
|
14
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
32
15
|
end
|
33
16
|
end
|
34
17
|
end
|
File without changes
|
data/lib/mailkick.rb
CHANGED
@@ -1,15 +1,25 @@
|
|
1
|
-
|
1
|
+
# dependencies
|
2
2
|
require "active_support"
|
3
3
|
|
4
|
-
|
4
|
+
# stdlib
|
5
|
+
require "set"
|
6
|
+
|
7
|
+
# modules
|
5
8
|
require "mailkick/model"
|
6
9
|
require "mailkick/service"
|
10
|
+
require "mailkick/service/aws_ses"
|
7
11
|
require "mailkick/service/mailchimp"
|
8
12
|
require "mailkick/service/mailgun"
|
9
13
|
require "mailkick/service/mandrill"
|
10
14
|
require "mailkick/service/sendgrid"
|
15
|
+
require "mailkick/service/sendgrid_v2"
|
16
|
+
require "mailkick/service/postmark"
|
17
|
+
require "mailkick/url_helper"
|
11
18
|
require "mailkick/version"
|
12
19
|
|
20
|
+
# integrations
|
21
|
+
require "mailkick/engine" if defined?(Rails)
|
22
|
+
|
13
23
|
module Mailkick
|
14
24
|
mattr_accessor :services, :user_method, :secret_token, :mount
|
15
25
|
self.services = []
|
@@ -56,17 +66,16 @@ module Mailkick
|
|
56
66
|
def self.opt_outs(options = {})
|
57
67
|
relation = Mailkick::OptOut.where(active: true)
|
58
68
|
|
59
|
-
|
60
|
-
binds = []
|
69
|
+
contact_relation = Mailkick::OptOut.none
|
61
70
|
if (email = options[:email])
|
62
|
-
|
63
|
-
binds << email
|
71
|
+
contact_relation = contact_relation.or(Mailkick::OptOut.where(email: email))
|
64
72
|
end
|
65
73
|
if (user = options[:user])
|
66
|
-
|
67
|
-
|
74
|
+
contact_relation = contact_relation.or(
|
75
|
+
Mailkick::OptOut.where("user_id = ? AND user_type = ?", user.id, user.class.name)
|
76
|
+
)
|
68
77
|
end
|
69
|
-
relation = relation.
|
78
|
+
relation = relation.merge(contact_relation) if email || user
|
70
79
|
|
71
80
|
relation =
|
72
81
|
if options[:list]
|
@@ -78,25 +87,33 @@ module Mailkick
|
|
78
87
|
relation
|
79
88
|
end
|
80
89
|
|
90
|
+
# TODO use keyword arguments
|
81
91
|
def self.opted_out_emails(options = {})
|
82
|
-
Set.new(opt_outs(options).where(
|
92
|
+
Set.new(opt_outs(options).where.not(email: nil).distinct.pluck(:email))
|
83
93
|
end
|
84
94
|
|
95
|
+
# TODO use keyword arguments
|
85
96
|
# does not take into account emails
|
86
97
|
def self.opted_out_users(options = {})
|
87
|
-
Set.new(opt_outs(options).where(
|
98
|
+
Set.new(opt_outs(options).where.not(user_id: nil).map(&:user))
|
88
99
|
end
|
89
100
|
|
90
101
|
def self.message_verifier
|
91
102
|
@message_verifier ||= ActiveSupport::MessageVerifier.new(Mailkick.secret_token)
|
92
103
|
end
|
93
104
|
|
94
|
-
def self.generate_token(email, list: nil)
|
95
|
-
|
105
|
+
def self.generate_token(email, user: nil, list: nil)
|
106
|
+
raise ArgumentError, "Missing email" unless email
|
107
|
+
|
108
|
+
user ||= Mailkick.user_method.call(email) if Mailkick.user_method
|
96
109
|
message_verifier.generate([email, user.try(:id), user.try(:class).try(:name), list])
|
97
110
|
end
|
98
111
|
end
|
99
112
|
|
113
|
+
ActiveSupport.on_load :action_mailer do
|
114
|
+
helper Mailkick::UrlHelper
|
115
|
+
end
|
116
|
+
|
100
117
|
ActiveSupport.on_load(:active_record) do
|
101
118
|
extend Mailkick::Model
|
102
119
|
end
|
data/lib/mailkick/engine.rb
CHANGED
@@ -7,7 +7,7 @@ module Mailkick
|
|
7
7
|
|
8
8
|
Mailkick.secret_token ||= begin
|
9
9
|
creds =
|
10
|
-
if app.respond_to?(:credentials)
|
10
|
+
if app.respond_to?(:credentials) && app.credentials.secret_key_base
|
11
11
|
app.credentials
|
12
12
|
elsif app.respond_to?(:secrets)
|
13
13
|
app.secrets
|
@@ -17,10 +17,6 @@ module Mailkick
|
|
17
17
|
|
18
18
|
creds.respond_to?(:secret_key_base) ? creds.secret_key_base : creds.secret_token
|
19
19
|
end
|
20
|
-
|
21
|
-
ActiveSupport.on_load :action_mailer do
|
22
|
-
helper Mailkick::UrlHelper
|
23
|
-
end
|
24
20
|
end
|
25
21
|
end
|
26
22
|
end
|
data/lib/mailkick/model.rb
CHANGED
@@ -3,8 +3,8 @@ module Mailkick
|
|
3
3
|
def mailkick_user(opts = {})
|
4
4
|
email_key = opts[:email_key] || :email
|
5
5
|
class_eval do
|
6
|
-
scope :opted_out,
|
7
|
-
binds = [
|
6
|
+
scope :opted_out, lambda { |options = {}|
|
7
|
+
binds = [name, true]
|
8
8
|
if options[:list]
|
9
9
|
query = "(mailkick_opt_outs.list IS NULL OR mailkick_opt_outs.list = ?)"
|
10
10
|
binds << options[:list]
|
@@ -13,20 +13,21 @@ module Mailkick
|
|
13
13
|
end
|
14
14
|
where("#{options[:not] ? 'NOT ' : ''}EXISTS(SELECT * FROM mailkick_opt_outs WHERE (#{table_name}.#{email_key} = mailkick_opt_outs.email OR (#{table_name}.#{primary_key} = mailkick_opt_outs.user_id AND mailkick_opt_outs.user_type = ?)) AND mailkick_opt_outs.active = ? AND #{query})", *binds)
|
15
15
|
}
|
16
|
-
|
16
|
+
|
17
|
+
scope :not_opted_out, lambda { |options = {}|
|
17
18
|
opted_out(options.merge(not: true))
|
18
19
|
}
|
19
20
|
|
20
|
-
|
21
|
-
Mailkick.opted_out?({email:
|
21
|
+
define_method :opted_out? do |options = {}|
|
22
|
+
Mailkick.opted_out?({email: send(email_key), user: self}.merge(options))
|
22
23
|
end
|
23
24
|
|
24
|
-
|
25
|
-
Mailkick.opt_out({email:
|
25
|
+
define_method :opt_out do |options = {}|
|
26
|
+
Mailkick.opt_out({email: send(email_key), user: self}.merge(options))
|
26
27
|
end
|
27
28
|
|
28
|
-
|
29
|
-
Mailkick.opt_in({email:
|
29
|
+
define_method :opt_in do |options = {}|
|
30
|
+
Mailkick.opt_in({email: send(email_key), user: self}.merge(options))
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SESV2/Client.html
|
2
|
+
|
3
|
+
module Mailkick
|
4
|
+
class Service
|
5
|
+
class AwsSes < Mailkick::Service
|
6
|
+
REASONS_MAP = {
|
7
|
+
"BOUNCE" => "bounce",
|
8
|
+
"COMPLAINT" => "spam"
|
9
|
+
}
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def opt_outs
|
16
|
+
response = client.list_suppressed_destinations({
|
17
|
+
reasons: ["BOUNCE", "COMPLAINT"],
|
18
|
+
# TODO make configurable
|
19
|
+
start_date: Time.now - (86400 * 365),
|
20
|
+
end_date: Time.now
|
21
|
+
})
|
22
|
+
|
23
|
+
opt_outs = []
|
24
|
+
response.each do |page|
|
25
|
+
page.suppressed_destination_summaries.each do |record|
|
26
|
+
opt_outs << {
|
27
|
+
email: record.email_address,
|
28
|
+
time: record.last_update_time,
|
29
|
+
reason: REASONS_MAP[record.reason]
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
opt_outs
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.discoverable?
|
37
|
+
!!defined?(::Aws::SESV2::Client)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def client
|
43
|
+
@client ||= ::Aws::SESV2::Client.new
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# https://github.com/wildbit/postmark-gem
|
2
|
+
|
3
|
+
module Mailkick
|
4
|
+
class Service
|
5
|
+
class Postmark < Mailkick::Service
|
6
|
+
REASONS_MAP = {
|
7
|
+
"SpamNotification" => "spam",
|
8
|
+
"SpamComplaint" => "spam",
|
9
|
+
"Unsubscribe" => "unsubscribe",
|
10
|
+
}
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@client = ::Postmark::ApiClient.new(options[:api_key] || ENV["POSTMARK_API_KEY"])
|
14
|
+
end
|
15
|
+
|
16
|
+
def opt_outs
|
17
|
+
bounces
|
18
|
+
end
|
19
|
+
|
20
|
+
def bounces
|
21
|
+
fetch(@client.bounces)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.discoverable?
|
25
|
+
!!(defined?(::Postmark) && ENV["POSTMARK_API_KEY"])
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def fetch(response)
|
31
|
+
response.map do |record|
|
32
|
+
{
|
33
|
+
email: record[:email],
|
34
|
+
time: ActiveSupport::TimeZone["UTC"].parse(record[:bounced_at]),
|
35
|
+
reason: REASONS_MAP.fetch(record[:type], "bounce")
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Mailkick
|
4
4
|
class Service
|
5
|
-
class
|
5
|
+
class SendGrid < Mailkick::Service
|
6
6
|
def initialize(options = {})
|
7
7
|
@api_user = options[:api_user] || ENV["SENDGRID_USERNAME"]
|
8
8
|
@api_key = options[:api_key] || ENV["SENDGRID_PASSWORD"]
|
@@ -41,5 +41,8 @@ module Mailkick
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
44
|
+
|
45
|
+
# backwards compatibility
|
46
|
+
Sendgrid = SendGrid
|
44
47
|
end
|
45
48
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# https://github.com/sendgrid/sendgrid-ruby
|
2
|
+
|
3
|
+
module Mailkick
|
4
|
+
class Service
|
5
|
+
class SendGridV2 < Mailkick::Service
|
6
|
+
def initialize(options = {})
|
7
|
+
@api_key = options[:api_key] || ENV["SENDGRID_API_KEY"]
|
8
|
+
end
|
9
|
+
|
10
|
+
def opt_outs
|
11
|
+
unsubscribes + spam_reports + bounces
|
12
|
+
end
|
13
|
+
|
14
|
+
def unsubscribes
|
15
|
+
fetch(client.suppression.unsubscribes, "unsubscribe")
|
16
|
+
end
|
17
|
+
|
18
|
+
def spam_reports
|
19
|
+
fetch(client.suppression.spam_reports, "spam")
|
20
|
+
end
|
21
|
+
|
22
|
+
def bounces
|
23
|
+
fetch(client.suppression.bounces, "bounce")
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.discoverable?
|
27
|
+
!!(defined?(::SendGrid::API) && ENV["SENDGRID_API_KEY"])
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def client
|
33
|
+
@client ||= ::SendGrid::API.new(api_key: @api_key).client
|
34
|
+
end
|
35
|
+
|
36
|
+
def fetch(query, reason)
|
37
|
+
# TODO paginate
|
38
|
+
response = query.get
|
39
|
+
|
40
|
+
raise "Bad status code: #{response.status_code}" if response.status_code.to_i != 200
|
41
|
+
|
42
|
+
response.parsed_body.map do |record|
|
43
|
+
{
|
44
|
+
email: record[:email],
|
45
|
+
time: Time.at(record[:created]),
|
46
|
+
reason: reason
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,12 +1,13 @@
|
|
1
1
|
module Mailkick
|
2
2
|
module UrlHelper
|
3
|
-
def mailkick_unsubscribe_url(email: nil, list: nil, **options)
|
4
|
-
email ||= controller.message.to.first
|
3
|
+
def mailkick_unsubscribe_url(email: nil, user: nil, list: nil, **options)
|
4
|
+
email ||= controller.try(:message).try(:to).try(:first)
|
5
|
+
|
5
6
|
Mailkick::Engine.routes.url_helpers.url_for(
|
6
7
|
(ActionMailer::Base.default_url_options || {}).merge(options).merge(
|
7
8
|
controller: "mailkick/subscriptions",
|
8
9
|
action: "unsubscribe",
|
9
|
-
id: Mailkick.generate_token(email, list: list)
|
10
|
+
id: Mailkick.generate_token(email, user: user, list: list)
|
10
11
|
)
|
11
12
|
)
|
12
13
|
end
|
data/lib/mailkick/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mailkick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-11-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '5'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: postmark
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
125
139
|
- !ruby/object:Gem::Dependency
|
126
140
|
name: combustion
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,54 +178,40 @@ dependencies:
|
|
164
178
|
- - ">="
|
165
179
|
- !ruby/object:Gem::Version
|
166
180
|
version: '0'
|
167
|
-
description:
|
168
|
-
email:
|
169
|
-
- andrew@chartkick.com
|
181
|
+
description:
|
182
|
+
email: andrew@chartkick.com
|
170
183
|
executables: []
|
171
184
|
extensions: []
|
172
185
|
extra_rdoc_files: []
|
173
186
|
files:
|
174
|
-
- ".gitignore"
|
175
|
-
- ".travis.yml"
|
176
187
|
- CHANGELOG.md
|
177
|
-
- Gemfile
|
178
188
|
- LICENSE.txt
|
179
189
|
- README.md
|
180
|
-
- Rakefile
|
181
190
|
- app/controllers/mailkick/subscriptions_controller.rb
|
182
|
-
- app/helpers/mailkick/url_helper.rb
|
183
191
|
- app/models/mailkick/opt_out.rb
|
184
192
|
- app/views/mailkick/subscriptions/show.html.erb
|
185
193
|
- config/routes.rb
|
186
194
|
- lib/generators/mailkick/install_generator.rb
|
187
|
-
- lib/generators/mailkick/templates/install.rb
|
195
|
+
- lib/generators/mailkick/templates/install.rb.tt
|
188
196
|
- lib/generators/mailkick/views_generator.rb
|
189
197
|
- lib/mailkick.rb
|
190
198
|
- lib/mailkick/engine.rb
|
191
199
|
- lib/mailkick/model.rb
|
192
200
|
- lib/mailkick/service.rb
|
201
|
+
- lib/mailkick/service/aws_ses.rb
|
193
202
|
- lib/mailkick/service/mailchimp.rb
|
194
203
|
- lib/mailkick/service/mailgun.rb
|
195
204
|
- lib/mailkick/service/mandrill.rb
|
205
|
+
- lib/mailkick/service/postmark.rb
|
196
206
|
- lib/mailkick/service/sendgrid.rb
|
207
|
+
- lib/mailkick/service/sendgrid_v2.rb
|
208
|
+
- lib/mailkick/url_helper.rb
|
197
209
|
- lib/mailkick/version.rb
|
198
|
-
- mailkick.gemspec
|
199
|
-
- test/gemfiles/actionmailer42.gemfile
|
200
|
-
- test/gemfiles/actionmailer50.gemfile
|
201
|
-
- test/gemfiles/actionmailer51.gemfile
|
202
|
-
- test/internal/app/mailers/user_mailer.rb
|
203
|
-
- test/internal/app/models/user.rb
|
204
|
-
- test/internal/app/views/user_mailer/welcome.html.erb
|
205
|
-
- test/internal/app/views/user_mailer/welcome.text.erb
|
206
|
-
- test/internal/config/database.yml
|
207
|
-
- test/internal/db/schema.rb
|
208
|
-
- test/mailkick_test.rb
|
209
|
-
- test/test_helper.rb
|
210
210
|
homepage: https://github.com/ankane/mailkick
|
211
211
|
licenses:
|
212
212
|
- MIT
|
213
213
|
metadata: {}
|
214
|
-
post_install_message:
|
214
|
+
post_install_message:
|
215
215
|
rdoc_options: []
|
216
216
|
require_paths:
|
217
217
|
- lib
|
@@ -219,27 +219,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
219
219
|
requirements:
|
220
220
|
- - ">="
|
221
221
|
- !ruby/object:Gem::Version
|
222
|
-
version: '
|
222
|
+
version: '2.4'
|
223
223
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
224
224
|
requirements:
|
225
225
|
- - ">="
|
226
226
|
- !ruby/object:Gem::Version
|
227
227
|
version: '0'
|
228
228
|
requirements: []
|
229
|
-
|
230
|
-
|
231
|
-
signing_key:
|
229
|
+
rubygems_version: 3.1.4
|
230
|
+
signing_key:
|
232
231
|
specification_version: 4
|
233
|
-
summary: Email
|
234
|
-
test_files:
|
235
|
-
- test/gemfiles/actionmailer42.gemfile
|
236
|
-
- test/gemfiles/actionmailer50.gemfile
|
237
|
-
- test/gemfiles/actionmailer51.gemfile
|
238
|
-
- test/internal/app/mailers/user_mailer.rb
|
239
|
-
- test/internal/app/models/user.rb
|
240
|
-
- test/internal/app/views/user_mailer/welcome.html.erb
|
241
|
-
- test/internal/app/views/user_mailer/welcome.text.erb
|
242
|
-
- test/internal/config/database.yml
|
243
|
-
- test/internal/db/schema.rb
|
244
|
-
- test/mailkick_test.rb
|
245
|
-
- test/test_helper.rb
|
232
|
+
summary: Email unsubscribes for Rails
|
233
|
+
test_files: []
|
data/.gitignore
DELETED
@@ -1,24 +0,0 @@
|
|
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
|
23
|
-
*.log
|
24
|
-
*.sqlite
|
data/.travis.yml
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
rvm:
|
3
|
-
- 2.4.2
|
4
|
-
sudo: false
|
5
|
-
script: bundle exec rake test
|
6
|
-
before_script:
|
7
|
-
- gem install bundler
|
8
|
-
notifications:
|
9
|
-
email:
|
10
|
-
on_success: never
|
11
|
-
on_failure: change
|
12
|
-
gemfile:
|
13
|
-
- Gemfile
|
14
|
-
- test/gemfiles/actionmailer51.gemfile
|
15
|
-
- test/gemfiles/actionmailer50.gemfile
|
16
|
-
- test/gemfiles/actionmailer42.gemfile
|
data/Gemfile
DELETED
data/Rakefile
DELETED
data/mailkick.gemspec
DELETED
@@ -1,32 +0,0 @@
|
|
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 = "Email subscriptions made easy"
|
12
|
-
spec.homepage = "https://github.com/ankane/mailkick"
|
13
|
-
spec.license = "MIT"
|
14
|
-
|
15
|
-
spec.files = `git ls-files -z`.split("\x0")
|
16
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
-
spec.require_paths = ["lib"]
|
19
|
-
|
20
|
-
spec.add_dependency "activesupport", ">= 4.2"
|
21
|
-
|
22
|
-
spec.add_development_dependency "bundler"
|
23
|
-
spec.add_development_dependency "gibbon", ">= 2"
|
24
|
-
spec.add_development_dependency "mailgun-ruby"
|
25
|
-
spec.add_development_dependency "mandrill-api"
|
26
|
-
spec.add_development_dependency "minitest"
|
27
|
-
spec.add_development_dependency "rake"
|
28
|
-
spec.add_development_dependency "sendgrid_toolkit"
|
29
|
-
spec.add_development_dependency "combustion"
|
30
|
-
spec.add_development_dependency "rails"
|
31
|
-
spec.add_development_dependency "sqlite3"
|
32
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
<p><%= mailkick_unsubscribe_url %></p>
|
@@ -1 +0,0 @@
|
|
1
|
-
Unsubscribe: <%= mailkick_unsubscribe_url %>
|
data/test/internal/db/schema.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
ActiveRecord::Schema.define do
|
2
|
-
create_table :mailkick_opt_outs do |t|
|
3
|
-
t.string :email
|
4
|
-
t.references :user, polymorphic: true
|
5
|
-
t.boolean :active, null: false, default: true
|
6
|
-
t.string :reason
|
7
|
-
t.string :list
|
8
|
-
t.timestamps
|
9
|
-
end
|
10
|
-
|
11
|
-
add_index :mailkick_opt_outs, :email
|
12
|
-
|
13
|
-
create_table :users do |t|
|
14
|
-
t.string :email
|
15
|
-
end
|
16
|
-
end
|
data/test/mailkick_test.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
require_relative "test_helper"
|
2
|
-
|
3
|
-
class MailkickTest < Minitest::Test
|
4
|
-
def test_unsubscribe_url
|
5
|
-
message = UserMailer.welcome.deliver_now
|
6
|
-
html_body = message.html_part.body.to_s
|
7
|
-
assert_includes html_body, "BAhbCUkiFXRlc3RAZXhhbXBsZS5vcmcGOgZFVDAwMA==--f435e91ba90e1732d3e999af1f2126dcc8182a5d"
|
8
|
-
text_body = message.text_part.body.to_s
|
9
|
-
assert_includes text_body, "BAhbCUkiFXRlc3RAZXhhbXBsZS5vcmcGOgZFVDAwMA==--f435e91ba90e1732d3e999af1f2126dcc8182a5d"
|
10
|
-
end
|
11
|
-
|
12
|
-
def test_opt_out
|
13
|
-
email = "test2@example.org"
|
14
|
-
user = User.create!(email: email)
|
15
|
-
|
16
|
-
Mailkick.opt_out(email: email, user: user)
|
17
|
-
|
18
|
-
opt_outs = Mailkick::OptOut.all.to_a
|
19
|
-
assert_equal 1, opt_outs.size
|
20
|
-
|
21
|
-
opt_out = opt_outs.first
|
22
|
-
assert_equal email, opt_out.email
|
23
|
-
assert_equal user, opt_out.user
|
24
|
-
|
25
|
-
assert user.opted_out?
|
26
|
-
assert_equal 1, User.opted_out.count
|
27
|
-
assert_equal 0, User.not_opted_out.count
|
28
|
-
end
|
29
|
-
end
|
data/test/test_helper.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
require "bundler/setup"
|
2
|
-
require "combustion"
|
3
|
-
Bundler.require(:default)
|
4
|
-
require "minitest/autorun"
|
5
|
-
require "minitest/pride"
|
6
|
-
require "logger"
|
7
|
-
|
8
|
-
Minitest::Test = Minitest::Unit::TestCase unless defined?(Minitest::Test)
|
9
|
-
|
10
|
-
Combustion.path = "test/internal"
|
11
|
-
Combustion.initialize! :all do
|
12
|
-
if config.active_record.sqlite3.respond_to?(:represent_boolean_as_integer)
|
13
|
-
config.active_record.sqlite3.represent_boolean_as_integer = false
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT) if ENV["VERBOSE"]
|
18
|
-
ActionMailer::Base.delivery_method = :test
|
19
|
-
|
20
|
-
Mailkick.secret_token = "test123"
|