ahoy_email 1.0.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -19
- data/LICENSE.txt +1 -1
- data/README.md +105 -57
- data/app/controllers/ahoy/messages_controller.rb +16 -9
- data/app/models/ahoy/message.rb +1 -1
- data/lib/ahoy_email.rb +10 -6
- data/lib/ahoy_email/engine.rb +3 -1
- data/lib/ahoy_email/mailer.rb +7 -3
- data/lib/ahoy_email/observer.rb +7 -0
- data/lib/ahoy_email/processor.rb +23 -11
- data/lib/ahoy_email/tracker.rb +5 -7
- data/lib/ahoy_email/version.rb +1 -1
- data/lib/generators/ahoy_email/install_generator.rb +3 -20
- data/lib/generators/ahoy_email/templates/install.rb.tt +1 -1
- metadata +13 -28
- data/lib/ahoy_email/interceptor.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2998bcf65131bcbb1ae5d6b5c2d0e1bf21496571a8c8c3e96230f21c9b3b2a74
|
4
|
+
data.tar.gz: 4e0291a50f134fcc95cdf8626aaf368bf2cb7c4ce763bbc008691d88421abba3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9fd0fd7d531e72f1c4d930cf35d398747ec7052b3f940597f80010e07f45d37c70ddb52f9d60d2c3b9bdb49a6760ed28a353e381f87e8849c57f7a5a6b6f8871
|
7
|
+
data.tar.gz: 04616042077a12cea5b86dd682339f91104c201bc0ce35a95716a45bf71cad4e80c964b0b57d813090d464d7cf66e7f4edc4f2c214c00723214342e00c9eccfd
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,29 @@
|
|
1
|
-
## 1.
|
1
|
+
## 1.1.1 (2021-03-06)
|
2
|
+
|
3
|
+
- Added support for classes for subscribers
|
4
|
+
- Use `datetime` type in migration
|
5
|
+
|
6
|
+
## 1.1.0 (2019-07-15)
|
7
|
+
|
8
|
+
- Made `opened_at` optional with click tracking
|
9
|
+
- Fixed secret token for environment variables
|
10
|
+
- Removed support for Rails 4.2
|
11
|
+
|
12
|
+
## 1.0.3 (2019-02-18)
|
13
|
+
|
14
|
+
- Fixed custom message model
|
15
|
+
- Fixed `message` option with proc
|
16
|
+
|
17
|
+
## 1.0.2 (2018-10-02)
|
18
|
+
|
19
|
+
- Fixed error with Ruby < 2.5
|
20
|
+
- Fixed UTM parameters storage on model
|
21
|
+
|
22
|
+
## 1.0.1 (2018-09-27)
|
23
|
+
|
24
|
+
- Use observer instead of interceptor
|
25
|
+
|
26
|
+
## 1.0.0 (2018-09-27)
|
2
27
|
|
3
28
|
- Removed support for Rails < 4.2
|
4
29
|
|
@@ -11,91 +36,91 @@ Breaking changes
|
|
11
36
|
- `AhoyEmail.track` was removed in favor of `AhoyEmail.default_options`
|
12
37
|
- The `heuristic_parse` option was removed and is now the default
|
13
38
|
|
14
|
-
## 0.5.2
|
39
|
+
## 0.5.2 (2018-04-26)
|
15
40
|
|
16
41
|
- Fixed secret token for Rails 5.2
|
17
42
|
- Added `heuristic_parse` option
|
18
43
|
|
19
|
-
## 0.5.1
|
44
|
+
## 0.5.1 (2018-04-19)
|
20
45
|
|
21
46
|
- Fixed deprecation warning in Rails 5.2
|
22
47
|
- Added `unsubscribe_links` option
|
23
48
|
- Allow `message_model` to accept a proc
|
24
49
|
- Use `references` in migration
|
25
50
|
|
26
|
-
## 0.5.0
|
51
|
+
## 0.5.0 (2017-05-01)
|
27
52
|
|
28
53
|
- Added support for Rails 5.1
|
29
54
|
- Added `invalid_redirect_url`
|
30
55
|
|
31
|
-
## 0.4.0
|
56
|
+
## 0.4.0 (2016-09-01)
|
32
57
|
|
33
58
|
- Fixed `belongs_to` error in Rails 5
|
34
59
|
- Include `safely_block` gem without polluting global namespace
|
35
60
|
|
36
|
-
## 0.3.2
|
61
|
+
## 0.3.2 (2016-07-27)
|
37
62
|
|
38
63
|
- Fixed deprecation warning for Rails 5
|
39
64
|
- Do not track content by default on fresh installations
|
40
65
|
|
41
|
-
## 0.3.1
|
66
|
+
## 0.3.1 (2016-05-11)
|
42
67
|
|
43
68
|
- Fixed deprecation warnings
|
44
69
|
- Fixed `stack level too deep` error
|
45
70
|
|
46
|
-
## 0.3.0
|
71
|
+
## 0.3.0 (2015-12-16)
|
47
72
|
|
48
73
|
- Added safely for error reporting
|
49
74
|
- Fixed error with `to`
|
50
75
|
- Prevent duplicate records when mail called multiple times
|
51
76
|
|
52
|
-
## 0.2.4
|
77
|
+
## 0.2.4 (2015-07-29)
|
53
78
|
|
54
79
|
- Added `extra` option for extra attributes
|
55
80
|
|
56
|
-
## 0.2.3
|
81
|
+
## 0.2.3 (2015-03-22)
|
57
82
|
|
58
83
|
- Save utm parameters
|
59
84
|
- Added `url_options`
|
60
85
|
- Skip tracking for `mailto` links
|
61
86
|
- Only set secret token if not already set
|
62
87
|
|
63
|
-
## 0.2.2
|
88
|
+
## 0.2.2 (2014-08-31)
|
64
89
|
|
65
90
|
- Fixed secret token for Rails 4.1
|
66
91
|
- Fixed links with href
|
67
92
|
- Fixed message id for Rails 3.1
|
68
93
|
|
69
|
-
## 0.2.1
|
94
|
+
## 0.2.1 (2014-05-26)
|
70
95
|
|
71
96
|
- Added `only` and `except` options
|
72
97
|
|
73
|
-
## 0.2.0
|
98
|
+
## 0.2.0 (2014-05-10)
|
74
99
|
|
75
100
|
- Enable tracking when track is called by default
|
76
101
|
|
77
|
-
## 0.1.5
|
102
|
+
## 0.1.5 (2014-05-09)
|
78
103
|
|
79
104
|
- Rails 3 fix
|
80
105
|
|
81
|
-
## 0.1.4
|
106
|
+
## 0.1.4 (2014-05-04)
|
82
107
|
|
83
108
|
- Try not to rewrite unsubscribe links
|
84
109
|
|
85
|
-
## 0.1.3
|
110
|
+
## 0.1.3 (2014-05-03)
|
86
111
|
|
87
112
|
- Added `to` and `mailer` fields
|
88
113
|
- Added subscribers for open and click events
|
89
114
|
|
90
|
-
## 0.1.2
|
115
|
+
## 0.1.2 (2014-05-01)
|
91
116
|
|
92
117
|
- Added `AhoyEmail.track` (fix)
|
93
118
|
|
94
|
-
## 0.1.1
|
119
|
+
## 0.1.1 (2014-04-30)
|
95
120
|
|
96
121
|
- Use secure compare for signature verification
|
97
122
|
- Fixed deprecation warnings
|
98
123
|
|
99
|
-
## 0.1.0
|
124
|
+
## 0.1.0 (2014-04-29)
|
100
125
|
|
101
126
|
- First major release
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,20 +1,12 @@
|
|
1
1
|
# Ahoy Email
|
2
2
|
|
3
|
-
|
3
|
+
First-party email analytics for Rails
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
- A history of emails sent to each user
|
8
|
-
- Easy UTM tagging
|
9
|
-
- Optional open and click tracking
|
10
|
-
|
11
|
-
**Ahoy Email 1.0 was recently released!** See [how to upgrade](#upgrading)
|
5
|
+
:fire: For web and native app analytics, check out [Ahoy](https://github.com/ankane/ahoy)
|
12
6
|
|
13
7
|
:bullettrain_side: To manage unsubscribes, check out [Mailkick](https://github.com/ankane/mailkick)
|
14
8
|
|
15
|
-
|
16
|
-
|
17
|
-
[![Build Status](https://travis-ci.org/ankane/ahoy_email.svg?branch=master)](https://travis-ci.org/ankane/ahoy_email)
|
9
|
+
[![Build Status](https://github.com/ankane/ahoy_email/workflows/build/badge.svg?branch=master)](https://github.com/ankane/ahoy_email/actions)
|
18
10
|
|
19
11
|
## Installation
|
20
12
|
|
@@ -31,14 +23,20 @@ rails generate ahoy_email:install
|
|
31
23
|
rails db:migrate
|
32
24
|
```
|
33
25
|
|
34
|
-
##
|
26
|
+
## Getting Started
|
27
|
+
|
28
|
+
There are three main features:
|
35
29
|
|
36
|
-
|
30
|
+
- [Message history](#message-history)
|
31
|
+
- [UTM tagging](#utm-tagging)
|
32
|
+
- [Open & click analytics](#open--click-analytics)
|
37
33
|
|
38
|
-
|
34
|
+
## Message History
|
35
|
+
|
36
|
+
Ahoy Email creates an `Ahoy::Message` record for each email sent by default. You can disable history for a mailer:
|
39
37
|
|
40
38
|
```ruby
|
41
|
-
class
|
39
|
+
class CouponMailer < ApplicationMailer
|
42
40
|
track message: false # use only/except to limit actions
|
43
41
|
end
|
44
42
|
```
|
@@ -51,14 +49,14 @@ AhoyEmail.default_options[:message] = false
|
|
51
49
|
|
52
50
|
### Users
|
53
51
|
|
54
|
-
Ahoy records the user a message is sent to - not just the email address. This gives you a
|
52
|
+
Ahoy Email records the user a message is sent to - not just the email address. This gives you a history of messages for each user, even if they change addresses.
|
55
53
|
|
56
|
-
By default, Ahoy tries `@user` then `params[:user]` then `User.find_by(email: message.to
|
54
|
+
By default, Ahoy tries `@user` then `params[:user]` then `User.find_by(email: message.to)` to find the user.
|
57
55
|
|
58
56
|
You can pass a specific user with:
|
59
57
|
|
60
58
|
```ruby
|
61
|
-
class
|
59
|
+
class CouponMailer < ApplicationMailer
|
62
60
|
track user: -> { params[:some_user] }
|
63
61
|
end
|
64
62
|
```
|
@@ -86,7 +84,7 @@ Record extra attributes on the `Ahoy::Message` model.
|
|
86
84
|
Create a migration to add extra attributes to the `ahoy_messages` table. For example:
|
87
85
|
|
88
86
|
```ruby
|
89
|
-
class AddCouponIdToAhoyMessages < ActiveRecord::Migration[
|
87
|
+
class AddCouponIdToAhoyMessages < ActiveRecord::Migration[6.1]
|
90
88
|
def change
|
91
89
|
add_column :ahoy_messages, :coupon_id, :integer
|
92
90
|
end
|
@@ -109,9 +107,15 @@ class CouponMailer < ApplicationMailer
|
|
109
107
|
end
|
110
108
|
```
|
111
109
|
|
112
|
-
|
110
|
+
## UTM Tagging
|
113
111
|
|
114
|
-
|
112
|
+
Use UTM tagging to attribute a conversion (like an order) to an email campaign. If you use [Ahoy](https://github.com/ankane/ahoy) for web analytics:
|
113
|
+
|
114
|
+
1. Send an email with UTM parameters
|
115
|
+
2. When a user visits the site, Ahoy will create a visit with the UTM parameters
|
116
|
+
3. When a user orders, the visit will be associated with the order (if [configured](https://github.com/ankane/ahoy#associated-models))
|
117
|
+
|
118
|
+
Add UTM parameters to links with:
|
115
119
|
|
116
120
|
```ruby
|
117
121
|
class CouponMailer < ApplicationMailer
|
@@ -139,22 +143,25 @@ Skip specific links with:
|
|
139
143
|
<%= link_to "Go", some_url, data: {skip_utm_params: true} %>
|
140
144
|
```
|
141
145
|
|
142
|
-
|
146
|
+
## Open & Click Analytics
|
143
147
|
|
144
|
-
|
148
|
+
While it’s nice to get feedback on the performance of your emails, we discourage the use of open tracking. If you do decide to use open or click tracking, be sure to get consent from your users and consider a short retention period. Check out [this article](https://www.eff.org/deeplinks/2019/01/stop-tracking-my-emails) for more best practices.
|
145
149
|
|
146
|
-
|
150
|
+
### Setup
|
147
151
|
|
148
152
|
Create a migration with:
|
149
153
|
|
150
154
|
```ruby
|
151
|
-
class AddTokenToAhoyMessages < ActiveRecord::Migration[
|
155
|
+
class AddTokenToAhoyMessages < ActiveRecord::Migration[6.1]
|
152
156
|
def change
|
153
157
|
add_column :ahoy_messages, :token, :string
|
158
|
+
add_index :ahoy_messages, :token
|
159
|
+
|
160
|
+
# for opens
|
154
161
|
add_column :ahoy_messages, :opened_at, :timestamp
|
155
|
-
add_column :ahoy_messages, :clicked_at, :timestamp
|
156
162
|
|
157
|
-
|
163
|
+
# for clicks
|
164
|
+
add_column :ahoy_messages, :clicked_at, :timestamp
|
158
165
|
end
|
159
166
|
end
|
160
167
|
```
|
@@ -168,12 +175,28 @@ AhoyEmail.api = true
|
|
168
175
|
And add to mailers you want to track:
|
169
176
|
|
170
177
|
```ruby
|
171
|
-
class
|
172
|
-
track open: true, click: true
|
178
|
+
class CouponMailer < ApplicationMailer
|
179
|
+
track open: true, click: true
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
Use only and except to limit actions
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
class CouponMailer < ApplicationMailer
|
187
|
+
track click: true, only: [:welcome]
|
173
188
|
end
|
174
189
|
```
|
175
190
|
|
176
|
-
|
191
|
+
Or make it conditional
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
class CouponMailer < ApplicationMailer
|
195
|
+
track click: -> { params[:user].opted_in? }
|
196
|
+
end
|
197
|
+
```
|
198
|
+
|
199
|
+
### How It Works
|
177
200
|
|
178
201
|
For opens, an invisible pixel is added right before the `</body>` tag in HTML emails. If the recipient has images enabled in their email client, the pixel is loaded and the open time recorded.
|
179
202
|
|
@@ -209,7 +232,7 @@ You can specify the domain to use with:
|
|
209
232
|
AhoyEmail.default_options[:url_options] = {host: "mydomain.com"}
|
210
233
|
```
|
211
234
|
|
212
|
-
|
235
|
+
### Events
|
213
236
|
|
214
237
|
Subscribe to open and click events by adding to the initializer:
|
215
238
|
|
@@ -243,6 +266,36 @@ end
|
|
243
266
|
AhoyEmail.subscribers << EmailSubscriber.new
|
244
267
|
```
|
245
268
|
|
269
|
+
## Data Protection
|
270
|
+
|
271
|
+
We recommend encrypting the `to` field (as well as the `subject` if it’s sensitive). [Lockbox](https://github.com/ankane/lockbox) is great for this. Use [Blind Index](https://github.com/ankane/blind_index) if you need to query by the `to` field.
|
272
|
+
|
273
|
+
Create `app/models/ahoy/message.rb` with:
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
class Ahoy::Message < ApplicationRecord
|
277
|
+
self.table_name = "ahoy_messages"
|
278
|
+
belongs_to :user, polymorphic: true, optional: true
|
279
|
+
|
280
|
+
encrypts :to
|
281
|
+
blind_index :to
|
282
|
+
end
|
283
|
+
```
|
284
|
+
|
285
|
+
## Data Retention
|
286
|
+
|
287
|
+
Delete older data with:
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
Ahoy::Message.where("created_at < ?", 1.year.ago).in_batches.delete_all
|
291
|
+
```
|
292
|
+
|
293
|
+
Delete data for a specific user with:
|
294
|
+
|
295
|
+
```ruby
|
296
|
+
Ahoy::Message.where(user_id: 1).in_batches.delete_all
|
297
|
+
```
|
298
|
+
|
246
299
|
## Reference
|
247
300
|
|
248
301
|
Set global options
|
@@ -265,36 +318,22 @@ AhoyEmail.track_method = lambda do |data|
|
|
265
318
|
end
|
266
319
|
```
|
267
320
|
|
268
|
-
##
|
269
|
-
|
270
|
-
### 1.0
|
271
|
-
|
272
|
-
Breaking changes
|
273
|
-
|
274
|
-
- UTM tagging, open tracking, and click tracking are no longer enabled by default. To enable, create an initializer with:
|
275
|
-
|
276
|
-
```ruby
|
277
|
-
AhoyEmail.api = true
|
278
|
-
|
279
|
-
AhoyEmail.default_options[:open] = true
|
280
|
-
AhoyEmail.default_options[:click] = true
|
281
|
-
AhoyEmail.default_options[:utm_params] = true
|
282
|
-
```
|
321
|
+
## Mongoid
|
283
322
|
|
284
|
-
|
285
|
-
- Proc options are now executed in the context of the mailer and take no arguments
|
323
|
+
If you prefer to use Mongoid instead of Active Record, create `app/models/ahoy/message.rb` with:
|
286
324
|
|
287
|
-
|
288
|
-
|
289
|
-
|
325
|
+
```ruby
|
326
|
+
class Ahoy::Message
|
327
|
+
include Mongoid::Document
|
290
328
|
|
291
|
-
|
292
|
-
user: -> { User.find_by(email: message.to.first) }
|
293
|
-
```
|
329
|
+
belongs_to :user, polymorphic: true, optional: true, index: true
|
294
330
|
|
295
|
-
|
296
|
-
|
297
|
-
|
331
|
+
field :to, type: String
|
332
|
+
field :mailer, type: String
|
333
|
+
field :subject, type: String
|
334
|
+
field :sent_at, type: Time
|
335
|
+
end
|
336
|
+
```
|
298
337
|
|
299
338
|
## History
|
300
339
|
|
@@ -308,3 +347,12 @@ Everyone is encouraged to help improve this project. Here are a few ways you can
|
|
308
347
|
- Fix bugs and [submit pull requests](https://github.com/ankane/ahoy_email/pulls)
|
309
348
|
- Write, clarify, or fix documentation
|
310
349
|
- Suggest or add new features
|
350
|
+
|
351
|
+
To get started with development:
|
352
|
+
|
353
|
+
```sh
|
354
|
+
git clone https://github.com/ankane/ahoy_email.git
|
355
|
+
cd ahoy_email
|
356
|
+
bundle install
|
357
|
+
bundle exec rake test
|
358
|
+
```
|
@@ -1,17 +1,14 @@
|
|
1
1
|
module Ahoy
|
2
2
|
class MessagesController < ApplicationController
|
3
3
|
filters = _process_action_callbacks.map(&:filter) - AhoyEmail.preserve_callbacks
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
skip_around_action(*filters, raise: false)
|
8
|
-
else
|
9
|
-
skip_action_callback *filters
|
10
|
-
end
|
4
|
+
skip_before_action(*filters, raise: false)
|
5
|
+
skip_after_action(*filters, raise: false)
|
6
|
+
skip_around_action(*filters, raise: false)
|
11
7
|
|
12
8
|
before_action :set_message
|
13
9
|
|
14
10
|
def open
|
11
|
+
# TODO move to MessageSubscriber in 2.0
|
15
12
|
if @message && !@message.opened_at
|
16
13
|
@message.opened_at = Time.now
|
17
14
|
@message.save!
|
@@ -23,9 +20,10 @@ module Ahoy
|
|
23
20
|
end
|
24
21
|
|
25
22
|
def click
|
23
|
+
# TODO move to MessageSubscriber in 2.0
|
26
24
|
if @message && !@message.clicked_at
|
27
25
|
@message.clicked_at = Time.now
|
28
|
-
@message.opened_at ||= @message.clicked_at
|
26
|
+
@message.opened_at ||= @message.clicked_at if @message.respond_to?(:opened_at=)
|
29
27
|
@message.save!
|
30
28
|
end
|
31
29
|
|
@@ -41,6 +39,7 @@ module Ahoy
|
|
41
39
|
|
42
40
|
redirect_to url
|
43
41
|
else
|
42
|
+
# TODO show link expired page with link to invalid redirect url in 2.0
|
44
43
|
redirect_to AhoyEmail.invalid_redirect_url || main_app.root_url
|
45
44
|
end
|
46
45
|
end
|
@@ -48,14 +47,22 @@ module Ahoy
|
|
48
47
|
protected
|
49
48
|
|
50
49
|
def set_message
|
51
|
-
@
|
50
|
+
@token = params[:id]
|
51
|
+
|
52
|
+
model = AhoyEmail.message_model
|
53
|
+
|
54
|
+
return if model.respond_to?(:column_names) && !model.column_names.include?("token")
|
55
|
+
|
56
|
+
@message = model.where(token: @token).first
|
52
57
|
end
|
53
58
|
|
54
59
|
def publish(name, event = {})
|
55
60
|
AhoyEmail.subscribers.each do |subscriber|
|
61
|
+
subscriber = subscriber.new if subscriber.is_a?(Class) && !subscriber.respond_to?(name)
|
56
62
|
if subscriber.respond_to?(name)
|
57
63
|
event[:message] = @message
|
58
64
|
event[:controller] = self
|
65
|
+
event[:token] = @token
|
59
66
|
subscriber.send name, event
|
60
67
|
end
|
61
68
|
end
|
data/app/models/ahoy/message.rb
CHANGED
data/lib/ahoy_email.rb
CHANGED
@@ -8,7 +8,7 @@ require "safely/core"
|
|
8
8
|
# modules
|
9
9
|
require "ahoy_email/processor"
|
10
10
|
require "ahoy_email/tracker"
|
11
|
-
require "ahoy_email/
|
11
|
+
require "ahoy_email/observer"
|
12
12
|
require "ahoy_email/mailer"
|
13
13
|
require "ahoy_email/version"
|
14
14
|
require "ahoy_email/engine" if defined?(Rails)
|
@@ -29,7 +29,7 @@ module AhoyEmail
|
|
29
29
|
utm_term: nil,
|
30
30
|
utm_content: nil,
|
31
31
|
utm_campaign: -> { action_name },
|
32
|
-
user: -> { @user || (respond_to?(:params) && params && params[:user]) || (message.to.size == 1 ? (User.find_by(email: message.to.first) rescue nil) : nil) },
|
32
|
+
user: -> { (defined?(@user) && @user) || (respond_to?(:params) && params && params[:user]) || (message.to.try(:size) == 1 ? (User.find_by(email: message.to.first) rescue nil) : nil) },
|
33
33
|
mailer: -> { "#{self.class.name}##{action_name}" },
|
34
34
|
url_options: {},
|
35
35
|
extra: {},
|
@@ -41,8 +41,7 @@ module AhoyEmail
|
|
41
41
|
|
42
42
|
ahoy_message = AhoyEmail.message_model.new
|
43
43
|
ahoy_message.to = Array(message.to).join(", ") if ahoy_message.respond_to?(:to=)
|
44
|
-
ahoy_message.
|
45
|
-
ahoy_message.user_id = data[:user_id]
|
44
|
+
ahoy_message.user = data[:user] if ahoy_message.respond_to?(:user=)
|
46
45
|
|
47
46
|
ahoy_message.mailer = data[:mailer] if ahoy_message.respond_to?(:mailer=)
|
48
47
|
ahoy_message.subject = message.subject if ahoy_message.respond_to?(:subject=)
|
@@ -58,14 +57,18 @@ module AhoyEmail
|
|
58
57
|
|
59
58
|
ahoy_message.sent_at = Time.now
|
60
59
|
ahoy_message.save!
|
60
|
+
|
61
|
+
ahoy_message
|
61
62
|
end
|
62
63
|
|
63
64
|
self.subscribers = []
|
64
65
|
|
65
66
|
self.preserve_callbacks = []
|
66
67
|
|
68
|
+
self.message_model = -> { ::Ahoy::Message }
|
69
|
+
|
67
70
|
def self.message_model
|
68
|
-
model =
|
71
|
+
model = defined?(@@message_model) && @@message_model
|
69
72
|
model = model.call if model.respond_to?(:call)
|
70
73
|
model
|
71
74
|
end
|
@@ -73,5 +76,6 @@ end
|
|
73
76
|
|
74
77
|
ActiveSupport.on_load(:action_mailer) do
|
75
78
|
include AhoyEmail::Mailer
|
76
|
-
|
79
|
+
register_observer AhoyEmail::Observer
|
80
|
+
Mail::Message.send(:attr_accessor, :ahoy_data, :ahoy_message)
|
77
81
|
end
|
data/lib/ahoy_email/engine.rb
CHANGED
@@ -13,7 +13,9 @@ module AhoyEmail
|
|
13
13
|
app.config
|
14
14
|
end
|
15
15
|
|
16
|
-
creds.respond_to?(:secret_key_base) ? creds.secret_key_base : creds.secret_token
|
16
|
+
token = creds.respond_to?(:secret_key_base) ? creds.secret_key_base : creds.secret_token
|
17
|
+
token ||= app.secret_key_base # should come first, but need to maintain backward compatibility
|
18
|
+
token
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
data/lib/ahoy_email/mailer.rb
CHANGED
@@ -24,10 +24,14 @@ module AhoyEmail
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def save_ahoy_options
|
27
|
-
|
28
|
-
|
27
|
+
Safely.safely do
|
28
|
+
# do message first for performance
|
29
|
+
message = ahoy_options[:message]
|
30
|
+
message = message.respond_to?(:call) ? instance_exec(&message) : message
|
31
|
+
|
32
|
+
if message
|
29
33
|
options = {}
|
30
|
-
ahoy_options.each do |k, v|
|
34
|
+
ahoy_options.except(:message).each do |k, v|
|
31
35
|
# execute options in mailer content
|
32
36
|
options[k] = v.respond_to?(:call) ? instance_exec(&v) : v
|
33
37
|
end
|
data/lib/ahoy_email/processor.rb
CHANGED
@@ -31,9 +31,11 @@ module AhoyEmail
|
|
31
31
|
def track_message
|
32
32
|
data = {
|
33
33
|
mailer: options[:mailer],
|
34
|
-
extra: options[:extra]
|
34
|
+
extra: options[:extra],
|
35
|
+
user: options[:user]
|
35
36
|
}
|
36
37
|
|
38
|
+
# legacy, remove in next major version
|
37
39
|
user = options[:user]
|
38
40
|
if user
|
39
41
|
data[:user_type] = user.model_name.name
|
@@ -46,17 +48,19 @@ module AhoyEmail
|
|
46
48
|
end
|
47
49
|
|
48
50
|
if options[:utm_params]
|
49
|
-
UTM_PARAMETERS.each do |k|
|
50
|
-
data[k] = options[k
|
51
|
+
UTM_PARAMETERS.map(&:to_sym).each do |k|
|
52
|
+
data[k] = options[k] if options[k]
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
54
|
-
mailer.message
|
56
|
+
mailer.message.ahoy_data = data
|
55
57
|
end
|
56
58
|
|
57
59
|
def track_open
|
58
60
|
if html_part?
|
59
|
-
|
61
|
+
part = message.html_part || message
|
62
|
+
raw_source = part.body.raw_source
|
63
|
+
|
60
64
|
regex = /<\/body>/i
|
61
65
|
url =
|
62
66
|
url_for(
|
@@ -69,18 +73,19 @@ module AhoyEmail
|
|
69
73
|
|
70
74
|
# try to add before body tag
|
71
75
|
if raw_source.match(regex)
|
72
|
-
raw_source.gsub
|
76
|
+
part.body = raw_source.gsub(regex, "#{pixel}\\0")
|
73
77
|
else
|
74
|
-
raw_source
|
78
|
+
part.body = raw_source + pixel
|
75
79
|
end
|
76
80
|
end
|
77
81
|
end
|
78
82
|
|
79
83
|
def track_links
|
80
84
|
if html_part?
|
81
|
-
|
85
|
+
part = message.html_part || message
|
82
86
|
|
83
|
-
|
87
|
+
# TODO use Nokogiri::HTML::DocumentFragment.parse in 2.0
|
88
|
+
doc = Nokogiri::HTML(part.body.raw_source)
|
84
89
|
doc.css("a[href]").each do |link|
|
85
90
|
uri = parse_uri(link["href"])
|
86
91
|
next unless trackable?(uri)
|
@@ -96,6 +101,8 @@ module AhoyEmail
|
|
96
101
|
end
|
97
102
|
|
98
103
|
if options[:click] && !skip_attribute?(link, "click")
|
104
|
+
raise "Secret token is empty" unless AhoyEmail.secret_token
|
105
|
+
|
99
106
|
# TODO sign more than just url and transition to HMAC-SHA256
|
100
107
|
signature = OpenSSL::HMAC.hexdigest("SHA1", AhoyEmail.secret_token, link["href"])
|
101
108
|
link["href"] =
|
@@ -109,8 +116,13 @@ module AhoyEmail
|
|
109
116
|
end
|
110
117
|
end
|
111
118
|
|
112
|
-
#
|
113
|
-
|
119
|
+
# ampersands converted to &
|
120
|
+
# https://github.com/sparklemotion/nokogiri/issues/1127
|
121
|
+
# not ideal, but should be equivalent in html5
|
122
|
+
# https://stackoverflow.com/questions/15776556/whats-the-difference-between-and-amp-in-html5
|
123
|
+
# escaping technically required before html5
|
124
|
+
# https://stackoverflow.com/questions/3705591/do-i-encode-ampersands-in-a-href
|
125
|
+
part.body = doc.to_s
|
114
126
|
end
|
115
127
|
end
|
116
128
|
|
data/lib/ahoy_email/tracker.rb
CHANGED
@@ -7,15 +7,13 @@ module AhoyEmail
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def perform
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
data
|
14
|
-
AhoyEmail.track_method.call(data)
|
10
|
+
Safely.safely do
|
11
|
+
# perform_deliveries check still needed in observer
|
12
|
+
if message.perform_deliveries && message.ahoy_data
|
13
|
+
data = message.ahoy_data.merge(message: message)
|
14
|
+
message.ahoy_message = AhoyEmail.track_method.call(data)
|
15
15
|
end
|
16
16
|
end
|
17
|
-
ensure
|
18
|
-
message["Ahoy-Message"] = nil if message["Ahoy-Message"]
|
19
17
|
end
|
20
18
|
end
|
21
19
|
end
|
data/lib/ahoy_email/version.rb
CHANGED
@@ -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 AhoyEmail
|
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/create_ahoy_messages.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
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ahoy_email
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
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: 2021-03-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionmailer
|
@@ -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: addressable
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,20 +136,6 @@ dependencies:
|
|
136
136
|
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
|
-
- !ruby/object:Gem::Dependency
|
140
|
-
name: rails
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
142
|
-
requirements:
|
143
|
-
- - ">="
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '0'
|
146
|
-
type: :development
|
147
|
-
prerelease: false
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
149
|
-
requirements:
|
150
|
-
- - ">="
|
151
|
-
- !ruby/object:Gem::Version
|
152
|
-
version: '0'
|
153
139
|
- !ruby/object:Gem::Dependency
|
154
140
|
name: sqlite3
|
155
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,8 +150,8 @@ dependencies:
|
|
164
150
|
- - ">="
|
165
151
|
- !ruby/object:Gem::Version
|
166
152
|
version: '0'
|
167
|
-
description:
|
168
|
-
email: andrew@
|
153
|
+
description:
|
154
|
+
email: andrew@ankane.org
|
169
155
|
executables: []
|
170
156
|
extensions: []
|
171
157
|
extra_rdoc_files: []
|
@@ -179,8 +165,8 @@ files:
|
|
179
165
|
- config/routes.rb
|
180
166
|
- lib/ahoy_email.rb
|
181
167
|
- lib/ahoy_email/engine.rb
|
182
|
-
- lib/ahoy_email/interceptor.rb
|
183
168
|
- lib/ahoy_email/mailer.rb
|
169
|
+
- lib/ahoy_email/observer.rb
|
184
170
|
- lib/ahoy_email/processor.rb
|
185
171
|
- lib/ahoy_email/tracker.rb
|
186
172
|
- lib/ahoy_email/version.rb
|
@@ -190,7 +176,7 @@ homepage: https://github.com/ankane/ahoy_email
|
|
190
176
|
licenses:
|
191
177
|
- MIT
|
192
178
|
metadata: {}
|
193
|
-
post_install_message:
|
179
|
+
post_install_message:
|
194
180
|
rdoc_options: []
|
195
181
|
require_paths:
|
196
182
|
- lib
|
@@ -198,16 +184,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
198
184
|
requirements:
|
199
185
|
- - ">="
|
200
186
|
- !ruby/object:Gem::Version
|
201
|
-
version: '2.
|
187
|
+
version: '2.4'
|
202
188
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
203
189
|
requirements:
|
204
190
|
- - ">="
|
205
191
|
- !ruby/object:Gem::Version
|
206
192
|
version: '0'
|
207
193
|
requirements: []
|
208
|
-
|
209
|
-
|
210
|
-
signing_key:
|
194
|
+
rubygems_version: 3.2.3
|
195
|
+
signing_key:
|
211
196
|
specification_version: 4
|
212
|
-
summary:
|
197
|
+
summary: First-party email analytics for Rails
|
213
198
|
test_files: []
|