mailkick 1.4.0 → 2.0.0
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 +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +27 -3
- data/app/controllers/mailkick/subscriptions_controller.rb +0 -16
- data/lib/generators/mailkick/install_generator.rb +12 -0
- data/lib/generators/mailkick/templates/install.rb.tt +2 -2
- data/lib/generators/mailkick/views_generator.rb +1 -1
- data/lib/mailkick/engine.rb +1 -17
- data/lib/mailkick/model.rb +7 -5
- data/lib/mailkick/service/aws_ses.rb +1 -2
- data/lib/mailkick/service/mailchimp.rb +3 -3
- data/lib/mailkick/service/mailgun.rb +3 -3
- data/lib/mailkick/service/mandrill.rb +2 -4
- data/lib/mailkick/service/postmark.rb +12 -11
- data/lib/mailkick/service/sendgrid.rb +21 -14
- data/lib/mailkick/version.rb +1 -1
- data/lib/mailkick.rb +3 -4
- metadata +3 -7
- data/app/models/mailkick/opt_out.rb +0 -7
- data/lib/mailkick/legacy.rb +0 -70
- data/lib/mailkick/serializer.rb +0 -15
- data/lib/mailkick/service/sendgrid_v2.rb +0 -52
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d3592c25635ef911ea75b5bd5f07bbf512cbc3066bda2a7a7deb39e0d969821
|
4
|
+
data.tar.gz: 521daa545720254493b7dbef3806a95e44ac739041e6a9ab4fd33a5ef41881b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb7e324a4812673f7278bb8ce890ea0d67ac74c6e923ae4d43dc0c6e447fa28282a56beeb25c86fe9d5e9e34d3ecda03099d2240773b5d74ca3a940cc4b8c6e5
|
7
|
+
data.tar.gz: ce6f5ba95efec816c0571189a3e2f1a99060fe389927656836110a750f61fe768810b7f2a342325081acb6d5b7e66f1464db1b449fbc5f1467a4e595d9a3c51c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## 2.0.0 (2025-06-23)
|
2
|
+
|
3
|
+
See the [upgrade notes](https://github.com/ankane/mailkick?tab=readme-ov-file#20)
|
4
|
+
|
5
|
+
- Added `prefix` option to `has_subscriptions`
|
6
|
+
- Switched to suppressions API for Postmark and changed default stream to `broadcast`
|
7
|
+
- Dropped support for legacy opt-outs
|
8
|
+
- Dropped support for legacy secret token generation and serialization
|
9
|
+
- Dropped support for legacy SendGrid service
|
10
|
+
|
1
11
|
## 1.4.0 (2025-04-03)
|
2
12
|
|
3
13
|
- Dropped support for Ruby < 3.2 and Rails < 7.1
|
data/README.md
CHANGED
@@ -5,6 +5,8 @@ Email subscriptions for Rails
|
|
5
5
|
- Add one-click unsubscribe links and headers to your emails
|
6
6
|
- Fetch bounces and spam reports from your email service
|
7
7
|
|
8
|
+
**Mailkick 2.0 was recently released** - see [how to upgrade](#upgrading)
|
9
|
+
|
8
10
|
:postbox: Check out [Ahoy Email](https://github.com/ankane/ahoy_email) for analytics
|
9
11
|
|
10
12
|
[](https://github.com/ankane/mailkick/actions)
|
@@ -126,8 +128,6 @@ The following services are supported:
|
|
126
128
|
- [Postmark](#postmark)
|
127
129
|
- [SendGrid](#sendgrid)
|
128
130
|
|
129
|
-
Will gladly accept pull requests for others.
|
130
|
-
|
131
131
|
#### AWS SES
|
132
132
|
|
133
133
|
Add the gem
|
@@ -168,7 +168,7 @@ Add the gem
|
|
168
168
|
gem "mandrill-api"
|
169
169
|
```
|
170
170
|
|
171
|
-
And set `ENV["
|
171
|
+
And set `ENV["MANDRILL_API_KEY"]`.
|
172
172
|
|
173
173
|
#### Postmark
|
174
174
|
|
@@ -209,6 +209,30 @@ Access the subscription model directly
|
|
209
209
|
Mailkick::Subscription.all
|
210
210
|
```
|
211
211
|
|
212
|
+
Prefix method names with `mailkick_`
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
class User < ApplicationRecord
|
216
|
+
has_subscriptions prefix: true
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
## Upgrading
|
221
|
+
|
222
|
+
### 2.0
|
223
|
+
|
224
|
+
Unsubscribe links created before version 1.1.1 will no longer work by default. Determine if this is acceptable for your application (for instance, in the US, links must work [for 30 days](https://www.ftc.gov/business-guidance/resources/can-spam-act-compliance-guide-business) after the message is sent). To restore support, [determine the previous secret token](https://github.com/ankane/mailkick/blob/v1.4.0/lib/mailkick/engine.rb#L13-L22) and create an initializer with:
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
Mailkick.message_verifier.rotate(previous_secret_token, serializer: Marshal)
|
228
|
+
```
|
229
|
+
|
230
|
+
If fetching bounces, spam reports, and unsubscribes from Postmark, the suppressions API is now used and the default stream has been changed to `broadcast`. To use `outbound`, create an initializer with:
|
231
|
+
|
232
|
+
```ruby
|
233
|
+
Mailkick.services = [Mailkick::Service::Postmark.new(api_key: api_key, stream_id: "outbound")]
|
234
|
+
```
|
235
|
+
|
212
236
|
## History
|
213
237
|
|
214
238
|
View the [changelog](https://github.com/ankane/mailkick/blob/master/CHANGELOG.md)
|
@@ -11,8 +11,6 @@ module Mailkick
|
|
11
11
|
def unsubscribe
|
12
12
|
subscription.delete_all
|
13
13
|
|
14
|
-
Mailkick::Legacy.opt_out(legacy_options) if Mailkick::Legacy.opt_outs?
|
15
|
-
|
16
14
|
if request.post? && params["List-Unsubscribe"] == "One-Click"
|
17
15
|
# must not redirect according to RFC 8058
|
18
16
|
# could render show action instead
|
@@ -25,8 +23,6 @@ module Mailkick
|
|
25
23
|
def subscribe
|
26
24
|
subscription.first_or_create!
|
27
25
|
|
28
|
-
Mailkick::Legacy.opt_in(legacy_options) if Mailkick::Legacy.opt_outs?
|
29
|
-
|
30
26
|
redirect_to subscription_path(params[:id])
|
31
27
|
end
|
32
28
|
|
@@ -65,17 +61,5 @@ module Mailkick
|
|
65
61
|
unsubscribe_subscription_path(params[:id])
|
66
62
|
end
|
67
63
|
helper_method :unsubscribe_url
|
68
|
-
|
69
|
-
def legacy_options
|
70
|
-
if @subscriber_type
|
71
|
-
# on the unprobabilistic chance subscriber_type is compromised, not much damage
|
72
|
-
user = @subscriber_type.constantize.find(@subscriber_id)
|
73
|
-
end
|
74
|
-
{
|
75
|
-
email: @email,
|
76
|
-
user: user,
|
77
|
-
list: @list
|
78
|
-
}
|
79
|
-
end
|
80
64
|
end
|
81
65
|
end
|
@@ -14,6 +14,18 @@ module Mailkick
|
|
14
14
|
def migration_version
|
15
15
|
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
16
16
|
end
|
17
|
+
|
18
|
+
def primary_key_type
|
19
|
+
", id: :#{key_type}" if key_type
|
20
|
+
end
|
21
|
+
|
22
|
+
def foreign_key_type
|
23
|
+
", type: :#{key_type}" if key_type
|
24
|
+
end
|
25
|
+
|
26
|
+
def key_type
|
27
|
+
Rails.configuration.generators.options.dig(:active_record, :primary_key_type)
|
28
|
+
end
|
17
29
|
end
|
18
30
|
end
|
19
31
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
|
-
create_table :mailkick_subscriptions do |t|
|
4
|
-
t.references :subscriber
|
3
|
+
create_table :mailkick_subscriptions<%= primary_key_type %> do |t|
|
4
|
+
t.references :subscriber<%= foreign_key_type %>, polymorphic: true, index: false
|
5
5
|
t.string :list
|
6
6
|
t.timestamps
|
7
7
|
end
|
@@ -3,7 +3,7 @@ require "rails/generators"
|
|
3
3
|
module Mailkick
|
4
4
|
module Generators
|
5
5
|
class ViewsGenerator < Rails::Generators::Base
|
6
|
-
source_root File.expand_path("
|
6
|
+
source_root File.expand_path("../../../app/views", __dir__)
|
7
7
|
|
8
8
|
def copy_initializer_file
|
9
9
|
directory "mailkick", "app/views/mailkick"
|
data/lib/mailkick/engine.rb
CHANGED
@@ -5,23 +5,7 @@ module Mailkick
|
|
5
5
|
initializer "mailkick" do |app|
|
6
6
|
Mailkick.discover_services unless Mailkick.services.any?
|
7
7
|
|
8
|
-
|
9
|
-
Mailkick.secret_token = app.key_generator.generate_key("mailkick")
|
10
|
-
Mailkick.message_verifier = ActiveSupport::MessageVerifier.new(Mailkick.secret_token, serializer: JSON)
|
11
|
-
|
12
|
-
# TODO remove in 2.0
|
13
|
-
creds =
|
14
|
-
if app.respond_to?(:credentials) && app.credentials.secret_key_base
|
15
|
-
app.credentials
|
16
|
-
elsif app.respond_to?(:secrets) && app.config.paths["config/secrets"].existent.any?
|
17
|
-
app.secrets
|
18
|
-
else
|
19
|
-
app.config
|
20
|
-
end
|
21
|
-
|
22
|
-
token = creds.respond_to?(:secret_key_base) ? creds.secret_key_base : creds.secret_token
|
23
|
-
Mailkick.message_verifier.rotate(token, serializer: Marshal) if token
|
24
|
-
end
|
8
|
+
Mailkick.secret_token ||= app.key_generator.generate_key("mailkick")
|
25
9
|
end
|
26
10
|
end
|
27
11
|
end
|
data/lib/mailkick/model.rb
CHANGED
@@ -1,21 +1,23 @@
|
|
1
1
|
module Mailkick
|
2
2
|
module Model
|
3
|
-
def has_subscriptions
|
3
|
+
def has_subscriptions(prefix: false)
|
4
|
+
prefix = prefix ? "mailkick_" : ""
|
5
|
+
|
4
6
|
class_eval do
|
5
7
|
has_many :mailkick_subscriptions, class_name: "Mailkick::Subscription", as: :subscriber
|
6
|
-
scope
|
8
|
+
scope "#{prefix}subscribed", ->(list) { joins(:mailkick_subscriptions).where(mailkick_subscriptions: {list: list}) }
|
7
9
|
|
8
|
-
|
10
|
+
define_method("#{prefix}subscribe") do |list|
|
9
11
|
mailkick_subscriptions.where(list: list).first_or_create!
|
10
12
|
nil
|
11
13
|
end
|
12
14
|
|
13
|
-
|
15
|
+
define_method("#{prefix}unsubscribe") do |list|
|
14
16
|
mailkick_subscriptions.where(list: list).delete_all
|
15
17
|
nil
|
16
18
|
end
|
17
19
|
|
18
|
-
|
20
|
+
define_method("#{prefix}subscribed?") do |list|
|
19
21
|
mailkick_subscriptions.where(list: list).exists?
|
20
22
|
end
|
21
23
|
end
|
@@ -3,9 +3,9 @@
|
|
3
3
|
module Mailkick
|
4
4
|
class Service
|
5
5
|
class Mailchimp < Mailkick::Service
|
6
|
-
def initialize(
|
7
|
-
@gibbon = ::Gibbon::Request.new(api_key:
|
8
|
-
@list_id =
|
6
|
+
def initialize(api_key: nil, list_id: nil)
|
7
|
+
@gibbon = ::Gibbon::Request.new(api_key: api_key || ENV["MAILCHIMP_API_KEY"])
|
8
|
+
@list_id = list_id || ENV["MAILCHIMP_LIST_ID"]
|
9
9
|
end
|
10
10
|
|
11
11
|
# TODO paginate
|
@@ -3,10 +3,10 @@
|
|
3
3
|
module Mailkick
|
4
4
|
class Service
|
5
5
|
class Mailgun < Mailkick::Service
|
6
|
-
def initialize(
|
6
|
+
def initialize(api_key: nil, domain: nil)
|
7
7
|
require "mailgun"
|
8
|
-
mailgun_client = ::Mailgun::Client.new(
|
9
|
-
domain
|
8
|
+
mailgun_client = ::Mailgun::Client.new(api_key || ENV["MAILGUN_API_KEY"])
|
9
|
+
domain ||= ActionMailer::Base.smtp_settings[:domain]
|
10
10
|
@mailgun_events = ::Mailgun::Events.new(mailgun_client, domain)
|
11
11
|
end
|
12
12
|
|
@@ -10,11 +10,10 @@ module Mailkick
|
|
10
10
|
"unsub" => "unsubscribe"
|
11
11
|
}
|
12
12
|
|
13
|
-
|
14
|
-
def initialize(options = {})
|
13
|
+
def initialize(api_key: nil)
|
15
14
|
require "mandrill"
|
16
15
|
@mandrill = ::Mandrill::API.new(
|
17
|
-
|
16
|
+
api_key || ENV["MANDRILL_APIKEY"] || ENV["MANDRILL_API_KEY"]
|
18
17
|
)
|
19
18
|
end
|
20
19
|
|
@@ -29,7 +28,6 @@ module Mailkick
|
|
29
28
|
end
|
30
29
|
end
|
31
30
|
|
32
|
-
# TODO remove ENV["MANDRILL_APIKEY"]
|
33
31
|
def self.discoverable?
|
34
32
|
!!(defined?(::Mandrill::API) && (ENV["MANDRILL_APIKEY"] || ENV["MANDRILL_API_KEY"]))
|
35
33
|
end
|
@@ -1,24 +1,25 @@
|
|
1
|
-
# https://github.com/
|
1
|
+
# https://github.com/ActiveCampaign/postmark-gem
|
2
2
|
|
3
3
|
module Mailkick
|
4
4
|
class Service
|
5
5
|
class Postmark < Mailkick::Service
|
6
6
|
REASONS_MAP = {
|
7
|
-
"
|
7
|
+
"HardBounce" => "bounce",
|
8
8
|
"SpamComplaint" => "spam",
|
9
|
-
"
|
9
|
+
"ManualSuppression" => "unsubscribe"
|
10
10
|
}
|
11
11
|
|
12
|
-
def initialize(
|
13
|
-
@client = ::Postmark::ApiClient.new(
|
12
|
+
def initialize(api_key: nil, stream_id: "broadcast")
|
13
|
+
@client = ::Postmark::ApiClient.new(api_key || ENV["POSTMARK_API_KEY"])
|
14
|
+
@stream_id = stream_id
|
14
15
|
end
|
15
16
|
|
16
17
|
def opt_outs
|
17
|
-
|
18
|
+
suppressions
|
18
19
|
end
|
19
20
|
|
20
|
-
def
|
21
|
-
fetch(@client.
|
21
|
+
def suppressions
|
22
|
+
fetch(@client.dump_suppressions(@stream_id))
|
22
23
|
end
|
23
24
|
|
24
25
|
def self.discoverable?
|
@@ -30,9 +31,9 @@ module Mailkick
|
|
30
31
|
def fetch(response)
|
31
32
|
response.map do |record|
|
32
33
|
{
|
33
|
-
email: record[:
|
34
|
-
time: ActiveSupport::TimeZone["UTC"].parse(record[:
|
35
|
-
reason: REASONS_MAP
|
34
|
+
email: record[:email_address],
|
35
|
+
time: ActiveSupport::TimeZone["UTC"].parse(record[:created_at]),
|
36
|
+
reason: REASONS_MAP[record[:suppression_reason]]
|
36
37
|
}
|
37
38
|
end
|
38
39
|
end
|
@@ -1,41 +1,48 @@
|
|
1
|
-
# https://github.com/
|
1
|
+
# https://github.com/sendgrid/sendgrid-ruby
|
2
2
|
|
3
3
|
module Mailkick
|
4
4
|
class Service
|
5
5
|
class SendGrid < Mailkick::Service
|
6
|
-
def initialize(
|
7
|
-
@
|
8
|
-
@api_key = options[:api_key] || ENV["SENDGRID_PASSWORD"]
|
6
|
+
def initialize(api_key: nil)
|
7
|
+
@api_key = api_key || ENV["SENDGRID_API_KEY"]
|
9
8
|
end
|
10
9
|
|
11
|
-
# TODO paginate
|
12
10
|
def opt_outs
|
13
11
|
unsubscribes + spam_reports + bounces
|
14
12
|
end
|
15
13
|
|
16
14
|
def unsubscribes
|
17
|
-
fetch(
|
15
|
+
fetch(client.suppression.unsubscribes, "unsubscribe")
|
18
16
|
end
|
19
17
|
|
20
18
|
def spam_reports
|
21
|
-
fetch(
|
19
|
+
fetch(client.suppression.spam_reports, "spam")
|
22
20
|
end
|
23
21
|
|
24
22
|
def bounces
|
25
|
-
fetch(
|
23
|
+
fetch(client.suppression.bounces, "bounce")
|
26
24
|
end
|
27
25
|
|
28
26
|
def self.discoverable?
|
29
|
-
!!(defined?(::
|
27
|
+
!!(defined?(::SendGrid::API) && ENV["SENDGRID_API_KEY"])
|
30
28
|
end
|
31
29
|
|
32
30
|
protected
|
33
31
|
|
34
|
-
def
|
35
|
-
|
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|
|
36
43
|
{
|
37
|
-
email: record[
|
38
|
-
time: record[
|
44
|
+
email: record[:email],
|
45
|
+
time: Time.at(record[:created]),
|
39
46
|
reason: reason
|
40
47
|
}
|
41
48
|
end
|
@@ -43,6 +50,6 @@ module Mailkick
|
|
43
50
|
end
|
44
51
|
|
45
52
|
# backwards compatibility
|
46
|
-
|
53
|
+
SendGridV2 = SendGrid
|
47
54
|
end
|
48
55
|
end
|
data/lib/mailkick/version.rb
CHANGED
data/lib/mailkick.rb
CHANGED
@@ -6,16 +6,13 @@ require "json"
|
|
6
6
|
require "set"
|
7
7
|
|
8
8
|
# modules
|
9
|
-
require_relative "mailkick/legacy"
|
10
9
|
require_relative "mailkick/model"
|
11
|
-
require_relative "mailkick/serializer"
|
12
10
|
require_relative "mailkick/service"
|
13
11
|
require_relative "mailkick/service/aws_ses"
|
14
12
|
require_relative "mailkick/service/mailchimp"
|
15
13
|
require_relative "mailkick/service/mailgun"
|
16
14
|
require_relative "mailkick/service/mandrill"
|
17
15
|
require_relative "mailkick/service/sendgrid"
|
18
|
-
require_relative "mailkick/service/sendgrid_v2"
|
19
16
|
require_relative "mailkick/service/postmark"
|
20
17
|
require_relative "mailkick/url_helper"
|
21
18
|
require_relative "mailkick/version"
|
@@ -34,12 +31,14 @@ module Mailkick
|
|
34
31
|
|
35
32
|
def self.fetch_opt_outs
|
36
33
|
services.each(&:fetch_opt_outs)
|
34
|
+
nil
|
37
35
|
end
|
38
36
|
|
39
37
|
def self.discover_services
|
40
38
|
Service.subclasses.each do |service|
|
41
39
|
services << service.new if service.discoverable?
|
42
40
|
end
|
41
|
+
nil
|
43
42
|
end
|
44
43
|
|
45
44
|
def self.secret_token=(token)
|
@@ -48,7 +47,7 @@ module Mailkick
|
|
48
47
|
end
|
49
48
|
|
50
49
|
def self.message_verifier
|
51
|
-
@@message_verifier ||= ActiveSupport::MessageVerifier.new(Mailkick.secret_token, serializer:
|
50
|
+
@@message_verifier ||= ActiveSupport::MessageVerifier.new(Mailkick.secret_token, serializer: JSON)
|
52
51
|
end
|
53
52
|
|
54
53
|
def self.generate_token(subscriber, list)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mailkick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activesupport
|
@@ -32,7 +32,6 @@ files:
|
|
32
32
|
- LICENSE.txt
|
33
33
|
- README.md
|
34
34
|
- app/controllers/mailkick/subscriptions_controller.rb
|
35
|
-
- app/models/mailkick/opt_out.rb
|
36
35
|
- app/models/mailkick/subscription.rb
|
37
36
|
- app/views/mailkick/subscriptions/show.html.erb
|
38
37
|
- config/routes.rb
|
@@ -41,9 +40,7 @@ files:
|
|
41
40
|
- lib/generators/mailkick/views_generator.rb
|
42
41
|
- lib/mailkick.rb
|
43
42
|
- lib/mailkick/engine.rb
|
44
|
-
- lib/mailkick/legacy.rb
|
45
43
|
- lib/mailkick/model.rb
|
46
|
-
- lib/mailkick/serializer.rb
|
47
44
|
- lib/mailkick/service.rb
|
48
45
|
- lib/mailkick/service/aws_ses.rb
|
49
46
|
- lib/mailkick/service/mailchimp.rb
|
@@ -51,7 +48,6 @@ files:
|
|
51
48
|
- lib/mailkick/service/mandrill.rb
|
52
49
|
- lib/mailkick/service/postmark.rb
|
53
50
|
- lib/mailkick/service/sendgrid.rb
|
54
|
-
- lib/mailkick/service/sendgrid_v2.rb
|
55
51
|
- lib/mailkick/url_helper.rb
|
56
52
|
- lib/mailkick/version.rb
|
57
53
|
homepage: https://github.com/ankane/mailkick
|
@@ -72,7 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
68
|
- !ruby/object:Gem::Version
|
73
69
|
version: '0'
|
74
70
|
requirements: []
|
75
|
-
rubygems_version: 3.6.
|
71
|
+
rubygems_version: 3.6.7
|
76
72
|
specification_version: 4
|
77
73
|
summary: Email subscriptions for Rails
|
78
74
|
test_files: []
|
data/lib/mailkick/legacy.rb
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
module Mailkick
|
2
|
-
module Legacy
|
3
|
-
# checks for table as long as it exists
|
4
|
-
def self.opt_outs?
|
5
|
-
unless defined?(@opt_outs) && @opt_outs == false
|
6
|
-
@opt_outs = ActiveRecord::Base.connection_pool.with_connection { |c| c.table_exists?("mailkick_opt_outs") }
|
7
|
-
end
|
8
|
-
@opt_outs
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.opted_out?(options)
|
12
|
-
opt_outs(options).any?
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.opt_out(options)
|
16
|
-
unless opted_out?(options)
|
17
|
-
time = options[:time] || Time.now
|
18
|
-
Mailkick::OptOut.create! do |o|
|
19
|
-
o.email = options[:email]
|
20
|
-
o.user = options[:user]
|
21
|
-
o.reason = options[:reason] || "unsubscribe"
|
22
|
-
o.list = options[:list]
|
23
|
-
o.created_at = time
|
24
|
-
o.updated_at = time
|
25
|
-
end
|
26
|
-
end
|
27
|
-
true
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.opt_in(options)
|
31
|
-
opt_outs(options).each do |opt_out|
|
32
|
-
opt_out.active = false
|
33
|
-
opt_out.save!
|
34
|
-
end
|
35
|
-
true
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.opt_outs(options = {})
|
39
|
-
relation = Mailkick::OptOut.where(active: true)
|
40
|
-
|
41
|
-
contact_relation = Mailkick::OptOut.none
|
42
|
-
if (email = options[:email])
|
43
|
-
contact_relation = contact_relation.or(Mailkick::OptOut.where(email: email))
|
44
|
-
end
|
45
|
-
if (user = options[:user])
|
46
|
-
contact_relation = contact_relation.or(
|
47
|
-
Mailkick::OptOut.where("user_id = ? AND user_type = ?", user.id, user.class.name)
|
48
|
-
)
|
49
|
-
end
|
50
|
-
relation = relation.merge(contact_relation) if email || user
|
51
|
-
|
52
|
-
relation =
|
53
|
-
if options[:list]
|
54
|
-
relation.where("list IS NULL OR list = ?", options[:list])
|
55
|
-
else
|
56
|
-
relation.where("list IS NULL")
|
57
|
-
end
|
58
|
-
|
59
|
-
relation
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.opted_out_emails(options = {})
|
63
|
-
Set.new(opt_outs(options).where.not(email: nil).distinct.pluck(:email))
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.opted_out_users(options = {})
|
67
|
-
Set.new(opt_outs(options).where.not(user_id: nil).map(&:user))
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
data/lib/mailkick/serializer.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
# need custom class due to
|
2
|
-
# https://github.com/rails/rails/issues/47185
|
3
|
-
module Mailkick
|
4
|
-
class Serializer
|
5
|
-
def self.dump(value)
|
6
|
-
ActiveSupport::JSON.encode(value)
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.load(value)
|
10
|
-
ActiveSupport::JSON.decode(value)
|
11
|
-
rescue JSON::ParserError
|
12
|
-
Marshal.load(value)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,52 +0,0 @@
|
|
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
|