mailkick 2.0.0 → 3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d3592c25635ef911ea75b5bd5f07bbf512cbc3066bda2a7a7deb39e0d969821
4
- data.tar.gz: 521daa545720254493b7dbef3806a95e44ac739041e6a9ab4fd33a5ef41881b7
3
+ metadata.gz: 53b4cfd8b344ad6273e734bad7531baaab340b50777e2e0590f2780b8ba72e96
4
+ data.tar.gz: 99d9c057e6fba352bc509368d49e4abda33fc6abd996ffef199866943e8625bf
5
5
  SHA512:
6
- metadata.gz: fb7e324a4812673f7278bb8ce890ea0d67ac74c6e923ae4d43dc0c6e447fa28282a56beeb25c86fe9d5e9e34d3ecda03099d2240773b5d74ca3a940cc4b8c6e5
7
- data.tar.gz: ce6f5ba95efec816c0571189a3e2f1a99060fe389927656836110a750f61fe768810b7f2a342325081acb6d5b7e66f1464db1b449fbc5f1467a4e595d9a3c51c
6
+ metadata.gz: 31c98f092a8fcf4f2deef3b6c2a1d385e1b7cc13df3d05ddaa35b9cd460c9d9fca966972abc59ba5cf20d3ff2442410ad809d109af1ddbb9c178ee14bb934701
7
+ data.tar.gz: 3b6b152bd2154d13fb88f5fe38d67297a4909c3b7f95b8401f7065020501f59eff2bad0642cd38be980f07df8b26506376c669d87c580886b765b521b59419bd
data/CHANGELOG.md CHANGED
@@ -1,6 +1,16 @@
1
+ ## 3.0.0 (2026-08-08)
2
+
3
+ See the [upgrade notes](https://github.com/ankane/mailkick?tab=readme-ov-file#30)
4
+
5
+ - Added `confirm_unsubscribe` option
6
+ - Enabled `headers` by default
7
+ - Improved CSP support
8
+ - Fixed change to key generator hash digest class
9
+ - Dropped support for Ruby < 3.3 and Rails < 7.2
10
+
1
11
  ## 2.0.0 (2025-06-23)
2
12
 
3
- See the [upgrade notes](https://github.com/ankane/mailkick?tab=readme-ov-file#20)
13
+ See the [upgrade notes](https://github.com/ankane/mailkick/blob/v2.0.0/README.md#20)
4
14
 
5
15
  - Added `prefix` option to `has_subscriptions`
6
16
  - Switched to suppressions API for Postmark and changed default stream to `broadcast`
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2025 Andrew Kane
1
+ Copyright (c) 2014-2026 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -5,7 +5,7 @@ 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)
8
+ **Mailkick 3.0 was recently released** - see [how to upgrade](#upgrading)
9
9
 
10
10
  :postbox: Check out [Ahoy Email](https://github.com/ankane/ahoy_email) for analytics
11
11
 
@@ -83,16 +83,20 @@ rails generate mailkick:views
83
83
 
84
84
  which copies the view into `app/views/mailkick`.
85
85
 
86
+ To add a confirmation step to prevent issues with scanners, create `config/initializers/mailkick.rb` with:
87
+
88
+ ```ruby
89
+ Mailkick.confirm_unsubscribe = true
90
+ ```
91
+
86
92
  ## Unsubscribe Headers
87
93
 
88
- For one-click unsubscribe headers ([RFC 8058](https://datatracker.ietf.org/doc/html/rfc8058)), create `config/initializers/mailkick.rb` with:
94
+ One-click unsubscribe headers ([RFC 8058](https://datatracker.ietf.org/doc/html/rfc8058)) are automatically added to emails that call `mailkick_unsubscribe_url`. To disable, create `config/initializers/mailkick.rb` with:
89
95
 
90
96
  ```ruby
91
- Mailkick.headers = true
97
+ Mailkick.headers = false
92
98
  ```
93
99
 
94
- Headers will automatically be added to emails that call `mailkick_unsubscribe_url`.
95
-
96
100
  ## Bounces and Spam Reports
97
101
 
98
102
  Fetch bounces, spam reports, and unsubscribes from your email service. Create `config/initializers/mailkick.rb` with a method to handle opt outs.
@@ -196,7 +200,7 @@ For more control over services, set them by hand.
196
200
 
197
201
  ```ruby
198
202
  Mailkick.services = [
199
- Mailkick::Service::SendGridV2.new(api_key: "API_KEY"),
203
+ Mailkick::Service::SendGrid.new(api_key: "API_KEY"),
200
204
  Mailkick::Service::Mailchimp.new(api_key: "API_KEY", list_id: "LIST_ID")
201
205
  ]
202
206
  ```
@@ -219,18 +223,20 @@ end
219
223
 
220
224
  ## Upgrading
221
225
 
222
- ### 2.0
226
+ ### 3.0
227
+
228
+ Versions 1.1.0-2.0.0 cause applications to use SHA1 for the key generator hash digest class if `Mailkick.secret_token` is not manually set (due to [this behavior in Rails](https://github.com/rails/rails/issues/56736)).
223
229
 
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:
230
+ To avoid breakage when upgrading, add to `config/application.rb`:
225
231
 
226
232
  ```ruby
227
- Mailkick.message_verifier.rotate(previous_secret_token, serializer: Marshal)
233
+ config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA1
228
234
  ```
229
235
 
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:
236
+ Also, one-click unsubscribe headers are enabled by default. To disable, create an initializer with:
231
237
 
232
238
  ```ruby
233
- Mailkick.services = [Mailkick::Service::Postmark.new(api_key: api_key, stream_id: "outbound")]
239
+ Mailkick.headers = false
234
240
  ```
235
241
 
236
242
  ## History
@@ -9,12 +9,16 @@ module Mailkick
9
9
  end
10
10
 
11
11
  def unsubscribe
12
- subscription.delete_all
12
+ defer = Mailkick.confirm_unsubscribe && request.get? && !params[:confirmed]
13
+
14
+ subscription.delete_all unless defer
13
15
 
14
16
  if request.post? && params["List-Unsubscribe"] == "One-Click"
15
17
  # must not redirect according to RFC 8058
16
18
  # could render show action instead
17
19
  render plain: "Unsubscribe successful"
20
+ elsif defer
21
+ render :show
18
22
  else
19
23
  redirect_to subscription_path(params[:id])
20
24
  end
@@ -52,14 +56,28 @@ module Mailkick
52
56
  end
53
57
  helper_method :opted_out?
54
58
 
59
+ def unsubscribing?
60
+ action_name == "unsubscribe"
61
+ end
62
+ helper_method :unsubscribing?
63
+
55
64
  def subscribe_url
56
65
  subscribe_subscription_path(params[:id])
57
66
  end
58
67
  helper_method :subscribe_url
59
68
 
69
+ # custom views created before 3.0 will use GET instead of POST
70
+ # add confirmed parameter to ensure unsubscribed
60
71
  def unsubscribe_url
61
- unsubscribe_subscription_path(params[:id])
72
+ unsubscribe_subscription_path(params[:id], {confirmed: true})
62
73
  end
63
74
  helper_method :unsubscribe_url
75
+
76
+ def style_nonce
77
+ if request.content_security_policy_nonce_directives&.include?("style-src")
78
+ content_security_policy_nonce
79
+ end
80
+ end
81
+ helper_method :style_nonce
64
82
  end
65
83
  end
@@ -3,33 +3,39 @@
3
3
  <head>
4
4
  <title>Subscriptions</title>
5
5
 
6
- <style>
6
+ <%= content_tag :style, nonce: style_nonce do %>
7
7
  body {
8
8
  padding: 20px;
9
- font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
9
+ font-family: system-ui, "Helvetica Neue", Arial, Helvetica, sans-serif;
10
10
  font-size: 14px;
11
11
  line-height: 1.4;
12
12
  }
13
13
 
14
- a {
15
- color: blue;
16
- }
17
-
18
14
  .container {
19
15
  max-width: 500px;
20
16
  margin-left: auto;
21
17
  margin-right: auto;
22
18
  }
23
- </style>
19
+
20
+ button {
21
+ cursor: pointer;
22
+ font: inherit;
23
+ padding: 5px 10px;
24
+ }
25
+ <% end %>
24
26
  </head>
25
27
  <body>
26
28
  <div class="container">
27
29
  <% if !subscribed? %>
28
30
  <p>You are unsubscribed.</p>
29
- <p><%= link_to "Resubscribe", subscribe_url %></p>
31
+ <p><%= button_to "Resubscribe", subscribe_url %></p>
30
32
  <% else %>
31
- <p>You are subscribed.</p>
32
- <p><%= link_to "Unsubscribe", unsubscribe_url %></p>
33
+ <% if unsubscribing? %>
34
+ <p>Click the button below to confirm.</p>
35
+ <% else %>
36
+ <p>You are subscribed.</p>
37
+ <% end %>
38
+ <p><%= button_to "Unsubscribe", unsubscribe_url %></p>
33
39
  <% end %>
34
40
  </div>
35
41
  </body>
data/config/routes.rb CHANGED
@@ -7,6 +7,7 @@ end
7
7
  Mailkick::Engine.routes.draw do
8
8
  resources :subscriptions, only: [:show] do
9
9
  match :unsubscribe, on: :member, via: [:get, :post]
10
- get :subscribe, on: :member
10
+ # support GET for custom views created before 3.0
11
+ match :subscribe, on: :member, via: [:get, :post]
11
12
  end
12
13
  end
@@ -5,7 +5,8 @@ module Mailkick
5
5
  initializer "mailkick" do |app|
6
6
  Mailkick.discover_services unless Mailkick.services.any?
7
7
 
8
- Mailkick.secret_token ||= app.key_generator.generate_key("mailkick")
8
+ # avoid app.key_generator due to https://github.com/ankane/ahoy_email/pull/168
9
+ Mailkick.secret_token ||= ActiveSupport::KeyGenerator.new(app.secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA1).generate_key("mailkick")
9
10
  end
10
11
  end
11
12
  end
@@ -1,3 +1,3 @@
1
1
  module Mailkick
2
- VERSION = "2.0.0"
2
+ VERSION = "3.0.0"
3
3
  end
data/lib/mailkick.rb CHANGED
@@ -21,13 +21,14 @@ require_relative "mailkick/version"
21
21
  require_relative "mailkick/engine" if defined?(Rails)
22
22
 
23
23
  module Mailkick
24
- mattr_accessor :services, :mount, :process_opt_outs_method, :headers
24
+ mattr_accessor :services, :mount, :process_opt_outs_method, :headers, :confirm_unsubscribe
25
25
  mattr_reader :secret_token
26
26
  mattr_writer :message_verifier
27
27
  self.services = []
28
28
  self.mount = true
29
29
  self.process_opt_outs_method = ->(_) { raise "process_opt_outs_method not defined" }
30
- self.headers = false
30
+ self.headers = true
31
+ self.confirm_unsubscribe = false
31
32
 
32
33
  def self.fetch_opt_outs
33
34
  services.each(&:fetch_opt_outs)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mailkick
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '7.1'
18
+ version: '7.2'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '7.1'
25
+ version: '7.2'
26
26
  email: andrew@ankane.org
27
27
  executables: []
28
28
  extensions: []
@@ -61,14 +61,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
61
  requirements:
62
62
  - - ">="
63
63
  - !ruby/object:Gem::Version
64
- version: '3.2'
64
+ version: '3.3'
65
65
  required_rubygems_version: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - ">="
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0'
70
70
  requirements: []
71
- rubygems_version: 3.6.7
71
+ rubygems_version: 4.0.16
72
72
  specification_version: 4
73
73
  summary: Email subscriptions for Rails
74
74
  test_files: []