ahoy_email 0.0.2 → 0.1.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
  SHA1:
3
- metadata.gz: 5c6f895cc438acdeb4982e684a4ba32efc86c05b
4
- data.tar.gz: 19750f27ae10e0828b4f3e7813c3a236413fa1b1
3
+ metadata.gz: 7d245ad6df5fbb64f6b38292b8b139a1382dda4e
4
+ data.tar.gz: cd822e8f05cf9eb01a1c37979b67428c2c7fd999
5
5
  SHA512:
6
- metadata.gz: 88ba353c711f596a745a8c406a222859c517c1392571791d91c833713d46a56a15f313428eae6af39e042b7c8ffc8aa1b75db969d1f950def5b824bb6b95fe19
7
- data.tar.gz: 082bba0e14fab78aba984e1d3e1b94ce08841c092ff24e834804bf15478c8cc02c2050df8ab1c15701530158d30954222261f86c7ac09abcbc24bca6ee4a62be
6
+ metadata.gz: 7c598c2697d2ca2167e2ccb6e5fdedb20a27f831f71b0f7ed0a4907d9b4646e24f6cd02cdbb7be54328dfec81cf9b353d57a3c80bd08cbf21a510b58e3da4463
7
+ data.tar.gz: a0649a7c6a0c575dc87a8aac7d36e1a53aab6d475c198a04131597001797442d1f3cdcedd442ec3f5f2ba4f64b3a2e4d9ab8d26af1d43d9984d7b060ea181d10
@@ -1,3 +1,3 @@
1
- ## 0.1.0 [unreleased]
1
+ ## 0.1.0
2
2
 
3
3
  - First major release
data/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # Ahoy Email
2
2
 
3
- :construction: Coming soon - May 1, 2014
3
+ :postbox: Simple, powerful email tracking for Rails
4
4
 
5
- :envelope: Simple, powerful email tracking for Rails
5
+ You get:
6
6
 
7
- Keep track of emails:
7
+ - A history of emails sent to each user
8
+ - Open and click tracking
9
+ - Easy UTM tagging
8
10
 
9
- - sent
10
- - opened
11
- - clicked
11
+ Works with any email service.
12
12
 
13
13
  ## Installation
14
14
 
@@ -27,17 +27,53 @@ rake db:migrate
27
27
 
28
28
  ## How It Works
29
29
 
30
- Ahoy creates an `Ahoy::Message` record when an email is sent.
30
+ Ahoy creates an `Ahoy::Message` every time an email is sent by default.
31
31
 
32
- ### Open
32
+ ### Users
33
33
 
34
- An invisible pixel is added right before the closing `</body>` tag to HTML emails.
34
+ Ahoy tracks the user a message is sent to - not just the email address. This gives you a full history of messages for each user, even if he or she changes addresses.
35
35
 
36
- If a recipient has images enabled in his / her email client, the pixel is loaded and an open is recorded.
36
+ By default, Ahoy tries `User.where(email: message.to.first).first` to find the user.
37
37
 
38
- ### Click
38
+ You can pass a specific user with:
39
39
 
40
- Links in HTML emails are rewritten to pass through your server.
40
+ ```ruby
41
+ class UserMailer < ActionMailer::Base
42
+ def welcome_email(user)
43
+ # ...
44
+ track user: user
45
+ mail to: user.email
46
+ end
47
+ end
48
+ ```
49
+
50
+ The user association is [polymorphic](http://railscasts.com/episodes/154-polymorphic-association), so use it with any model.
51
+
52
+ To get all messages sent to a user, add an association:
53
+
54
+ ```ruby
55
+ class User < ActiveRecord::Base
56
+ has_many :messages, class_name: "Ahoy::Message"
57
+ end
58
+ ```
59
+
60
+ And run:
61
+
62
+ ```ruby
63
+ user.messages
64
+ ```
65
+
66
+ ### Opens
67
+
68
+ An invisible pixel is added right before the `</body>` tag in HTML emails.
69
+
70
+ If the recipient has images enabled in his or her email client, the pixel is loaded and the open time recorded.
71
+
72
+ Use `track open: false` to skip this.
73
+
74
+ ### Clicks
75
+
76
+ A redirect is added to links to track clicks in HTML emails.
41
77
 
42
78
  ````
43
79
  http://chartkick.com
@@ -46,78 +82,94 @@ http://chartkick.com
46
82
  becomes
47
83
 
48
84
  ```
49
- http://yoursite.com/ahoy/messages/rAnDoMtOken/click?url=http%3A%2F%2Fchartkick.com&signature=...
85
+ http://you.io/ahoy/messages/rAnDoMtOkEn/click?url=http%3A%2F%2Fchartkick.com&signature=...
50
86
  ```
51
87
 
52
88
  A signature is added to prevent [open redirects](https://www.owasp.org/index.php/Open_redirect).
53
89
 
54
- Keep specific links from being tracked with `<a data-disable-tracking="true" href="..."></a>`.
90
+ Use `track click: false` to skip tracking, or skip specific links with:
91
+
92
+ ```html
93
+ <a data-skip-click="true" href="...">Can't touch this</a>
94
+ ```
55
95
 
56
96
  ### UTM Parameters
57
97
 
58
- UTM parameters are added to each link if they don’t already exist.
98
+ UTM parameters are added to links if they don’t already exist.
59
99
 
60
- By default, `utm_medium` is set to `email`.
100
+ The defaults are:
61
101
 
62
- ### User
102
+ - utm_medium - `email`
103
+ - utm_source - the mailer name like `user_mailer`
104
+ - utm_campaign - the mailer action like `welcome_email`
63
105
 
64
- To specify the user, use:
106
+ Use `track utm_params: false` to skip tagging, or skip specific links with:
65
107
 
66
- ```ruby
108
+
109
+ ```html
110
+ <a data-skip-utm-params="true" href="...">Break it down</a>
111
+ ```
112
+
113
+ ## Customize
114
+
115
+ There are 3 places to set options. Here’s the order of precedence.
116
+
117
+ ### Action
118
+
119
+ ``` ruby
67
120
  class UserMailer < ActionMailer::Base
68
121
  def welcome_email(user)
69
122
  # ...
70
- ahoy user: user
123
+ track user: user
71
124
  mail to: user.email
72
125
  end
73
126
  end
74
127
  ```
75
128
 
76
- User is [polymorphic](http://railscasts.com/episodes/154-polymorphic-association), so use it with any model.
77
-
78
- ## Customize
129
+ ### Mailer
79
130
 
80
- There are 3 places to set options.
131
+ ```ruby
132
+ class UserMailer < ActionMailer::Base
133
+ track utm_campaign: "boom"
134
+ end
135
+ ```
81
136
 
82
137
  ### Global
83
138
 
84
- The defaults are listed below.
139
+ ```ruby
140
+ AhoyEmail.track open: false
141
+ ```
142
+
143
+ ## Reference
144
+
145
+ You can use a `Proc` for any option.
85
146
 
86
147
  ```ruby
87
- AhoyEmail.options = {
88
- create_message: true,
89
- track_open: true,
90
- track_click: true,
91
- utm_source: nil,
92
- utm_medium: "email",
93
- utm_term: nil,
94
- utm_content: nil,
95
- utm_campaign: nil
96
- }
148
+ track utm_campaign: proc{|message, mailer| mailer.action_name + Time.now.year }
97
149
  ```
98
150
 
99
- ### Mailers
151
+ Disable tracking for an email
100
152
 
101
153
  ```ruby
102
- class UserMailer < ActionMailer::Base
103
- ahoy utm_campaign: "boom"
104
- end
154
+ track message: false
105
155
  ```
106
156
 
107
- ### Action
157
+ Or by default
108
158
 
109
- ``` ruby
110
- class UserMailer < ActionMailer::Base
111
- def welcome_email(user)
112
- # ...
113
- ahoy user: user
114
- mail to: user.email
115
- end
116
- end
159
+ ```ruby
160
+ AhoyEmail.track message: false
161
+ ```
162
+
163
+ Use a different model
164
+
165
+ ```ruby
166
+ AhoyEmail.message_model = UserMessage
117
167
  ```
118
168
 
119
169
  ## TODO
120
170
 
171
+ - Add tests
172
+ - Open and click hooks for deeper analytics
121
173
  - Subscription management (lists, opt-outs) [separate gem]
122
174
 
123
175
  ## History
@@ -27,7 +27,7 @@ module Ahoy
27
27
  protected
28
28
 
29
29
  def set_message
30
- @message = Ahoy::Message.where(token: params[:id]).first
30
+ @message = AhoyEmail.message_model.where(token: params[:id]).first
31
31
  end
32
32
 
33
33
  end
@@ -5,49 +5,34 @@ require "addressable/uri"
5
5
  require "openssl"
6
6
  require "ahoy_email/processor"
7
7
  require "ahoy_email/interceptor"
8
+ require "ahoy_email/mailer"
8
9
  require "ahoy_email/engine"
9
10
 
10
- ActionMailer::Base.register_interceptor AhoyEmail::Interceptor
11
-
12
11
  module AhoyEmail
13
12
  mattr_accessor :secret_token, :options
14
13
 
15
14
  self.options = {
16
- create_message: true,
17
- track_open: true,
18
- track_click: true,
19
- utm_source: nil,
15
+ message: true,
16
+ open: true,
17
+ click: true,
18
+ utm_params: true,
19
+ utm_source: proc {|message, mailer| mailer.mailer_name },
20
20
  utm_medium: "email",
21
21
  utm_term: nil,
22
22
  utm_content: nil,
23
- utm_campaign: nil
23
+ utm_campaign: proc {|message, mailer| mailer.action_name },
24
+ user: proc{|message, mailer| User.where(email: message.to.first).first rescue nil }
24
25
  }
25
- end
26
-
27
- module ActionMailer
28
- class Base
29
- class_attribute :ahoy_options
30
- self.ahoy_options = {}
31
-
32
- class << self
33
- def ahoy(options)
34
- self.ahoy_options = ahoy_options.merge(options)
35
- end
36
- end
37
-
38
- def ahoy(options)
39
- @ahoy_options = (@ahoy_options || {}).merge(options)
40
- end
41
26
 
42
- def mail_with_ahoy(headers = {}, &block)
43
- message = mail_without_ahoy(headers, &block)
44
-
45
- options = AhoyEmail.options.merge(self.class.ahoy_options).merge(@ahoy_options || {})
46
- AhoyEmail::Processor.new(message, options).process!
47
-
48
- message
49
- end
50
- alias_method_chain :mail, :ahoy
27
+ def self.message_model=(message_model)
28
+ @message_model = message_model
29
+ end
51
30
 
31
+ def self.message_model
32
+ @message_model || Ahoy::Message
52
33
  end
34
+
53
35
  end
36
+
37
+ ActionMailer::Base.send :include, AhoyEmail::Mailer
38
+ ActionMailer::Base.register_interceptor AhoyEmail::Interceptor
@@ -3,7 +3,7 @@ module AhoyEmail
3
3
  class << self
4
4
 
5
5
  def delivering_email(message)
6
- AhoyEmail::Processor.new(message).mark_sent!
6
+ AhoyEmail::Processor.new(message).track_send
7
7
  end
8
8
 
9
9
  end
@@ -0,0 +1,38 @@
1
+ module AhoyEmail
2
+ module Mailer
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ base.class_eval do
7
+ class_attribute :ahoy_options
8
+ self.ahoy_options = {}
9
+ alias_method_chain :mail, :ahoy
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ def track(options)
15
+ self.ahoy_options = ahoy_options.merge(options)
16
+ end
17
+ end
18
+
19
+ def track(options)
20
+ @ahoy_options = (@ahoy_options || {}).merge(options)
21
+ end
22
+
23
+ def mail_with_ahoy(headers = {}, &block)
24
+ message = mail_without_ahoy(headers, &block)
25
+
26
+ options = AhoyEmail.options.merge(self.class.ahoy_options).merge(@ahoy_options || {})
27
+ options.each do |k, v|
28
+ if v.respond_to?(:call)
29
+ options[k] = v.call(message, self)
30
+ end
31
+ end
32
+ AhoyEmail::Processor.new(message, options).process
33
+
34
+ message
35
+ end
36
+
37
+ end
38
+ end
@@ -7,31 +7,30 @@ module AhoyEmail
7
7
  def initialize(message, options = {})
8
8
  @message = message
9
9
  @options = options
10
- @ahoy_message = Ahoy::Message.new
11
10
  end
12
11
 
13
- def process!
14
- if options[:create_message]
12
+ def process
13
+ if options[:message]
14
+ @ahoy_message = AhoyEmail.message_model.new
15
15
  ahoy_message.token = generate_token
16
16
  ahoy_message.user = options[:user]
17
17
 
18
- track_utm_parameters!
19
- track_open! if options[:track_open]
20
- track_click! if options[:track_click]
18
+ track_open if options[:open]
19
+ track_links if options[:utm_params] or options[:click]
21
20
 
22
21
  # save
23
22
  ahoy_message.subject = message.subject if ahoy_message.respond_to?(:subject=)
24
23
  ahoy_message.content = message.to_s if ahoy_message.respond_to?(:content=)
25
- ahoy_message.sent_at = Time.now
26
24
  ahoy_message.save
25
+ message["Ahoy-Message-Id"] = ahoy_message.id
27
26
  end
28
27
  rescue => e
29
28
  report_error(e)
30
29
  end
31
30
 
32
- def mark_sent!
31
+ def track_send
33
32
  if (message_id = message["Ahoy-Message-Id"])
34
- ahoy_message = Ahoy::Message.where(id: message_id).first
33
+ ahoy_message = AhoyEmail.message_model.where(id: message_id.to_s).first
35
34
  if ahoy_message
36
35
  ahoy_message.sent_at = Time.now
37
36
  ahoy_message.save
@@ -48,27 +47,7 @@ module AhoyEmail
48
47
  SecureRandom.urlsafe_base64(32).gsub(/[\-_]/, "").first(32)
49
48
  end
50
49
 
51
- def track_utm_parameters!
52
- if html_part?
53
- body = (message.html_part || message).body
54
-
55
- doc = Nokogiri::HTML(body.raw_source)
56
- doc.css("a").each do |link|
57
- uri = Addressable::URI.parse(link["href"])
58
- params = uri.query_values || {}
59
- %w[utm_source utm_medium utm_term utm_content utm_campaign].each do |key|
60
- params[key] ||= options[key.to_sym] if options[key.to_sym]
61
- end
62
- uri.query_values = params
63
- link["href"] = uri.to_s
64
- end
65
-
66
- # hacky
67
- body.raw_source.sub!(body.raw_source, doc.to_s)
68
- end
69
- end
70
-
71
- def track_open!
50
+ def track_open
72
51
  if html_part?
73
52
  raw_source = (message.html_part || message).body.raw_source
74
53
  regex = /<\/body>/i
@@ -92,17 +71,24 @@ module AhoyEmail
92
71
  end
93
72
  end
94
73
 
95
- def track_click!
74
+ def track_links
96
75
  if html_part?
97
76
  body = (message.html_part || message).body
98
77
 
99
78
  doc = Nokogiri::HTML(body.raw_source)
100
79
  doc.css("a").each do |link|
101
- key = "data-disable-tracking"
102
- if link[key]
103
- # remove attribute
104
- link.remove_attribute(key)
105
- else
80
+ # utm params first
81
+ if options[:utm_params] and !skip_attribute?(link, "utm-params")
82
+ uri = Addressable::URI.parse(link["href"])
83
+ params = uri.query_values || {}
84
+ %w[utm_source utm_medium utm_term utm_content utm_campaign].each do |key|
85
+ params[key] ||= options[key.to_sym] if options[key.to_sym]
86
+ end
87
+ uri.query_values = params
88
+ link["href"] = uri.to_s
89
+ end
90
+
91
+ if options[:click] and !skip_attribute?(link, "click")
106
92
  signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new("sha1"), AhoyEmail.secret_token, link["href"])
107
93
  url =
108
94
  AhoyEmail::Engine.routes.url_helpers.url_for(
@@ -128,6 +114,17 @@ module AhoyEmail
128
114
  (message.html_part || message).content_type =~ /html/
129
115
  end
130
116
 
117
+ def skip_attribute?(link, suffix)
118
+ attribute = "data-skip-#{suffix}"
119
+ if link[attribute]
120
+ # remove it
121
+ link.remove_attribute(attribute)
122
+ true
123
+ else
124
+ false
125
+ end
126
+ end
127
+
131
128
  # not a fan of quiet errors
132
129
  # but tracking should *not* break
133
130
  # email delivery in production
@@ -1,3 +1,3 @@
1
1
  module AhoyEmail
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -8,6 +8,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration
8
8
  t.string :user_type
9
9
 
10
10
  # optional
11
+ # feel free to remove
11
12
  t.text :subject
12
13
  t.text :content
13
14
 
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: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-29 00:00:00.000000000 Z
11
+ date: 2014-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionmailer
@@ -100,6 +100,7 @@ files:
100
100
  - lib/ahoy_email.rb
101
101
  - lib/ahoy_email/engine.rb
102
102
  - lib/ahoy_email/interceptor.rb
103
+ - lib/ahoy_email/mailer.rb
103
104
  - lib/ahoy_email/processor.rb
104
105
  - lib/ahoy_email/version.rb
105
106
  - lib/generators/ahoy_email/install_generator.rb