mail_jack 0.2.1
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.
- data/.document +5 -0
- data/.gitignore +49 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +51 -0
- data/Rakefile +53 -0
- data/lib/mail_jack.rb +94 -0
- data/lib/mail_jack/config.rb +19 -0
- data/lib/mail_jack/interceptor.rb +17 -0
- data/lib/mail_jack/mailer.rb +22 -0
- data/lib/mail_jack/params_decoder.rb +30 -0
- data/lib/mail_jack/railtie.rb +5 -0
- data/lib/mail_jack/version.rb +4 -0
- data/mail_jack.gemspec +18 -0
- metadata +59 -0
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# rcov generated
|
2
|
+
coverage
|
3
|
+
coverage.data
|
4
|
+
|
5
|
+
# rdoc generated
|
6
|
+
rdoc
|
7
|
+
|
8
|
+
# yard generated
|
9
|
+
doc
|
10
|
+
.yardoc
|
11
|
+
|
12
|
+
# bundler
|
13
|
+
.bundle
|
14
|
+
|
15
|
+
# jeweler generated
|
16
|
+
pkg
|
17
|
+
|
18
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
19
|
+
#
|
20
|
+
# * Create a file at ~/.gitignore
|
21
|
+
# * Include files you want ignored
|
22
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
23
|
+
#
|
24
|
+
# After doing this, these files will be ignored in all your git projects,
|
25
|
+
# saving you from having to 'pollute' every project you touch with them
|
26
|
+
#
|
27
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
28
|
+
#
|
29
|
+
# For MacOS:
|
30
|
+
#
|
31
|
+
#.DS_Store
|
32
|
+
|
33
|
+
# For TextMate
|
34
|
+
#*.tmproj
|
35
|
+
#tmtags
|
36
|
+
|
37
|
+
# For emacs:
|
38
|
+
#*~
|
39
|
+
#\#*
|
40
|
+
#.\#*
|
41
|
+
|
42
|
+
# For vim:
|
43
|
+
#*.swp
|
44
|
+
|
45
|
+
# For redcar:
|
46
|
+
#.redcar
|
47
|
+
|
48
|
+
# For rubinius:
|
49
|
+
#*.rbc
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Peter P
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
= MailJack
|
2
|
+
|
3
|
+
Like LoJack...get it?
|
4
|
+
|
5
|
+
This module will append query parameters of your choosing to every link
|
6
|
+
in the mailers you specify so that you can track click throughs. The parameters
|
7
|
+
are dynamically evaluated at mail send time with the Proc of your choosing. Cool, huh.
|
8
|
+
|
9
|
+
== Usage:
|
10
|
+
|
11
|
+
# config/initializers/mail_tracker.rb
|
12
|
+
MailJack.config do |config|
|
13
|
+
|
14
|
+
# specify what mailer classes you want to add tracking to
|
15
|
+
config.mailers = [:user_notifier]
|
16
|
+
|
17
|
+
# specify the regex that should be used filter hrefs
|
18
|
+
# (this is useful so you don't add tracking params to 3rd party urls off your site)
|
19
|
+
config.href_filter = /myapplication.com/
|
20
|
+
|
21
|
+
# MOST IMPORTANT PART
|
22
|
+
# Specify what attributes you want to track and the Proc that figures them out
|
23
|
+
# The attributes can be any name. The value must be an object that responds to #call
|
24
|
+
config.trackable do |track|
|
25
|
+
track.campaign = lambda{|mailer| mailer.action_name}
|
26
|
+
track.campaign_group = lambda{|mailer| mailer.class.name}
|
27
|
+
track.foobarbizbat = lambda{|mailer| Time.now}
|
28
|
+
end
|
29
|
+
|
30
|
+
# You can enable Base64 encoding of all the parameters like so:
|
31
|
+
config.encode_to = :your_param_name_that_will_hold_the_encoded_string
|
32
|
+
end
|
33
|
+
|
34
|
+
== Under the Covers
|
35
|
+
|
36
|
+
* You specify what mailer classes you want to track, and any other options
|
37
|
+
* You specify the attributes that will be tracked the Proc that should be used to figure out the value
|
38
|
+
* Mail::Message class has the trackable keys added as attr_accessors
|
39
|
+
* ActionMailer::Base#mail method is decorated via alias_method_chain for each mailer class specified
|
40
|
+
* MailJack registers a Mail::Interceptor to intercept all outgoing mail
|
41
|
+
* When ActionMailer::Base#mail is called, the undecorated method is called first, then MailJack
|
42
|
+
* fetches the values of the attributes specified by calling the Proc specified and passing in the mailer
|
43
|
+
* instance, so You can figure out what value should be returned
|
44
|
+
* Those values are then assigned to the Mail::Message class via the accessors we added in step 3
|
45
|
+
* The mail is sent and is subsequently intercepted by MailJack::Interceptor which then reads the values passed along into the Mail::Message, creates a query string, and finds all relevant href's and appends the tracking parameters
|
46
|
+
|
47
|
+
== Copyright
|
48
|
+
|
49
|
+
Copyright (c) 2013 Peter Philips. See LICENSE.txt for
|
50
|
+
further details.
|
51
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "mail-jack"
|
18
|
+
gem.homepage = "http://github.com/synth/mail-jack"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{TODO: one-line summary of your gem}
|
21
|
+
gem.description = %Q{TODO: longer description of your gem}
|
22
|
+
gem.email = "pete@p373.net"
|
23
|
+
gem.authors = ["Peter P"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/test_*.rb'
|
39
|
+
test.verbose = true
|
40
|
+
test.rcov_opts << '--exclude "gems/*"'
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rdoc/task'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "mail-jack #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/lib/mail_jack.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'mail_jack/config'
|
2
|
+
require 'mail_jack/mailer'
|
3
|
+
require 'mail_jack/interceptor'
|
4
|
+
require 'mail_jack/params_decoder'
|
5
|
+
require 'mail_jack/railtie'
|
6
|
+
##################################################################################
|
7
|
+
#
|
8
|
+
# module MailJack
|
9
|
+
#
|
10
|
+
# - like LoJack...get it?
|
11
|
+
#
|
12
|
+
# This module will append query parameters of your choosing to every link
|
13
|
+
# in the mailers you specify so that you can track click throughs. The parameters
|
14
|
+
# are dynamically evaluated at mail send time with the Proc of your choosing. Cool, huh.
|
15
|
+
#
|
16
|
+
# Usage:
|
17
|
+
#
|
18
|
+
# # config/initializers/mail_tracker.rb
|
19
|
+
# MailJack.config do |config|
|
20
|
+
#
|
21
|
+
# # specify what mailer classes you want to add tracking to
|
22
|
+
# config.mailers = [:user_notifier]
|
23
|
+
#
|
24
|
+
# # specify the regex that should be used filter hrefs
|
25
|
+
# # (this is useful so you don't add tracking params to 3rd party urls off your site)
|
26
|
+
# config.href_filter = /myapplication.com/
|
27
|
+
#
|
28
|
+
# # MOST IMPORTANT PART
|
29
|
+
# # Specify what attributes you want to track and the Proc that figures them out
|
30
|
+
# # The attributes can be any name. The value must be an object that responds to #call
|
31
|
+
# config.trackable do |track|
|
32
|
+
# track.campaign = lambda{|mailer| mailer.action_name}
|
33
|
+
# track.campaign_group = lambda{|mailer| mailer.class.name}
|
34
|
+
# track.foobarbizbat = lambda{|mailer| Time.now}
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# Under the Covers
|
39
|
+
#
|
40
|
+
# 1. You specify what mailer classes you want to track, and any other options
|
41
|
+
# 2. You specify the attributes that will be tracked the Proc that should be used to figure out the value
|
42
|
+
# 3. Mail::Message class has the trackable keys added as attr_accessors
|
43
|
+
# 4. ActionMailer::Base#mail method is decorated via alias_method_chain for each mailer class specified
|
44
|
+
# 5. MailJack registers a Mail::Interceptor to intercept all outgoing mail
|
45
|
+
# 5. When ActionMailer::Base#mail is called, the undecorated method is called first, then MailJack
|
46
|
+
# fetches the values of the attributes specified by calling the Proc specified and passing in the mailer
|
47
|
+
# instance, so You can figure out what value should be returned
|
48
|
+
# 6. Those values are then assigned to the Mail::Message class via the accessors we added in step 3
|
49
|
+
# 7. The mail is sent and is subsequently intercepted by MailJack::Interceptor which then reads the
|
50
|
+
# the values passed along into the Mail::Message, creates a query string, and finds all relevant href's
|
51
|
+
# and appends the tracking parameters
|
52
|
+
#
|
53
|
+
##################################################################################
|
54
|
+
module MailJack
|
55
|
+
def self.config
|
56
|
+
|
57
|
+
# return configuration if already configured
|
58
|
+
return @@config if defined?(@@config) and @@config.kind_of?(Config)
|
59
|
+
|
60
|
+
# yield configuration
|
61
|
+
@@config = Config.new
|
62
|
+
yield @@config
|
63
|
+
|
64
|
+
# dynamically define the accessors onto Mail::Message
|
65
|
+
# so we can assign them later in the Interceptor
|
66
|
+
Mail::Message.class_eval do
|
67
|
+
attr_accessor *@@config.trackables.keys
|
68
|
+
end
|
69
|
+
|
70
|
+
# include the module that decorates(ie monkey patches)
|
71
|
+
# the Mail::Message#mail method
|
72
|
+
@@config.mailers.each do |mailer|
|
73
|
+
mailer.to_s.classify.constantize.send(:include, Mailer)
|
74
|
+
end
|
75
|
+
|
76
|
+
# register the interceptor
|
77
|
+
Mail.register_interceptor(MailJack::Interceptor)
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
# Fetch attributes gets the dynamically defined attributes
|
82
|
+
# off the object passed in, the object is ActionMailer::Base instance
|
83
|
+
def self.fetch_attributes(mailer)
|
84
|
+
map = {}
|
85
|
+
self.trackables.each do |attribute, proc|
|
86
|
+
map[attribute] = proc.call(mailer)
|
87
|
+
end
|
88
|
+
return map
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.trackables
|
92
|
+
@@config.trackables
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module MailJack
|
2
|
+
class Config
|
3
|
+
attr_accessor :mailers, :href_filter, :trackables, :encode_to
|
4
|
+
def trackable
|
5
|
+
|
6
|
+
# use OpenStruct for sugary assignment
|
7
|
+
@trackables = OpenStruct.new
|
8
|
+
yield @trackables
|
9
|
+
|
10
|
+
#convert from a struct to a map
|
11
|
+
@trackables = @trackables.methods(false).map(&:to_s).reject{|m| m.match(/\=$/) }.inject({}) {|hash, m| hash[m.to_sym] = @trackables.send(m); hash}
|
12
|
+
end
|
13
|
+
|
14
|
+
def enable_encoding?
|
15
|
+
self.encode_to.present?
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module MailJack
|
2
|
+
class Interceptor
|
3
|
+
def self.delivering_email(message)
|
4
|
+
body = message.body.to_s
|
5
|
+
hrefs = body.scan(/href=\"(.*?)\"/).flatten
|
6
|
+
hrefs = hrefs.grep(MailJack.config.href_filter)
|
7
|
+
hrefs = hrefs.uniq
|
8
|
+
|
9
|
+
querystr = MailJack.trackables.keys.inject({}) {|hash, a| hash[a] = message.send(a); hash}.to_query
|
10
|
+
hrefs.each do |h|
|
11
|
+
q = "?#{querystr}" unless h.include?("?")
|
12
|
+
body.gsub!(h, "#{h}#{q}")
|
13
|
+
end
|
14
|
+
message.body = body
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MailJack
|
2
|
+
module Mailer
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
alias_method_chain :mail, :tracking
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def mail_with_tracking(*args)
|
10
|
+
message = mail_without_tracking(*args)
|
11
|
+
|
12
|
+
# get a map of the required attributes
|
13
|
+
attributes = MailJack.fetch_attributes(self)
|
14
|
+
|
15
|
+
# assign them to the mail message
|
16
|
+
attributes.each do |key, val|
|
17
|
+
message.send("#{key}=", val)
|
18
|
+
end
|
19
|
+
return message
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module MailJack
|
2
|
+
class ParamsDecoder
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
decode_params(env) if MailJack.config.enable_encoding?
|
9
|
+
@status, @headers, @response = @app.call(env)
|
10
|
+
[@status, @headers, @response]
|
11
|
+
end
|
12
|
+
|
13
|
+
def decode_params(env)
|
14
|
+
param_name = MailJack.config.encode_to
|
15
|
+
return unless env['QUERY_STRING'].present? and env['QUERY_STRING'].match(/#{param_name}\=/)
|
16
|
+
|
17
|
+
params = {'QUERY_STRING' => env["QUERY_STRING"], 'REQUEST_URI' => env["REQUEST_URI"].split("?")[1]}
|
18
|
+
|
19
|
+
params.each do |key, value|
|
20
|
+
next unless value.present? and value.match(/#{param_name}\=/)
|
21
|
+
|
22
|
+
hash = Rack::Utils.parse_query(value)
|
23
|
+
encoded = hash.delete(param_name.to_s)
|
24
|
+
decoded = Base64.decode64(encoded)
|
25
|
+
|
26
|
+
env[key].gsub!("#{param_name}=#{encoded}", decoded)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/mail_jack.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "mail_jack/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.authors = ["Peter Philips"]
|
7
|
+
gem.description = %q{Autogenerate query parameters to links in emails to track click throughs}
|
8
|
+
gem.email = ['pete@p373.net']
|
9
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
|
10
|
+
gem.files = `git ls-files`.split("\n")
|
11
|
+
gem.homepage = 'https://github.com/synth/mail_jack'
|
12
|
+
gem.name = 'mail_jack'
|
13
|
+
gem.require_paths = ['lib']
|
14
|
+
gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
|
15
|
+
gem.summary = %q{Autogenerate query parameters to links in emails to track click throughs}
|
16
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
gem.version = MailJack::VERSION.dup
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mail_jack
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Peter Philips
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-06 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Autogenerate query parameters to links in emails to track click throughs
|
15
|
+
email:
|
16
|
+
- pete@p373.net
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .document
|
22
|
+
- .gitignore
|
23
|
+
- Gemfile
|
24
|
+
- LICENSE.txt
|
25
|
+
- README.rdoc
|
26
|
+
- Rakefile
|
27
|
+
- lib/mail_jack.rb
|
28
|
+
- lib/mail_jack/config.rb
|
29
|
+
- lib/mail_jack/interceptor.rb
|
30
|
+
- lib/mail_jack/mailer.rb
|
31
|
+
- lib/mail_jack/params_decoder.rb
|
32
|
+
- lib/mail_jack/railtie.rb
|
33
|
+
- lib/mail_jack/version.rb
|
34
|
+
- mail_jack.gemspec
|
35
|
+
homepage: https://github.com/synth/mail_jack
|
36
|
+
licenses: []
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.3.6
|
53
|
+
requirements: []
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 1.8.25
|
56
|
+
signing_key:
|
57
|
+
specification_version: 3
|
58
|
+
summary: Autogenerate query parameters to links in emails to track click throughs
|
59
|
+
test_files: []
|