action_mailer_pixel 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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +51 -0
- data/Rakefile +22 -0
- data/app/controllers/action_mailer_pixel/application_controller.rb +5 -0
- data/app/controllers/action_mailer_pixel/pixel_controller.rb +28 -0
- data/app/models/action_mailer_pixel/application_record.rb +5 -0
- data/app/models/action_mailer_pixel/email_view.rb +11 -0
- data/app/models/action_mailer_pixel/pixel.rb +11 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20181018105200_create_email_views.rb +19 -0
- data/lib/action_mailer_pixel.rb +29 -0
- data/lib/action_mailer_pixel/dsl/tracking_pixel.rb +62 -0
- data/lib/action_mailer_pixel/engine.rb +18 -0
- data/lib/action_mailer_pixel/interceptor.rb +59 -0
- data/lib/action_mailer_pixel/version.rb +3 -0
- data/lib/generators/action_mailer_pixel/install_generator.rb +15 -0
- data/lib/generators/templates/action_mailer_pixel_initializer.rb +9 -0
- data/lib/tasks/action_mailer_pixel_tasks.rake +4 -0
- metadata +119 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 20847b2dc90ebaac48117f71451ab0bfd3534de66f4dbd4327b6401e774292a9
|
4
|
+
data.tar.gz: f140fe438ffb84b1badfcda8360ac7dacffad20889167a5b191bea6df55d062e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: '0294b8df34fc4857d5dde7bb50d224b7a263e747bccf7c68bd6c0d065bc8e12cf88e5a07332ce5b696c4cf834e81adaaa69f3ee043ede99b3cdb6ca8f7eb9dd3'
|
7
|
+
data.tar.gz: da4cd791942b75581e0ebb929c6b986328c472494e59170ff156b339a2288400929c2feb5ccdded43cfbf3926304b1c46280c1fecf7fc89e68080e04f04057b9
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 Veselin Stoyanov
|
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.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# ActionMailerPixel
|
2
|
+
ActionMailerPixel allows you to easily track your mailers' open rate.
|
3
|
+
|
4
|
+
## How it works
|
5
|
+
When you enable the open tracking pixel on a mailer, a simple **img** tag will be inserted right before the closing body tag upon delivery. The source of the image tag leads to the /open.png path wherever you mounted the engine in your application. The response is a 1x1px transparent PNG image. When the email is opened by an email client, the pixel might get rendered (depending on the security/privacy settings) and the opening will be saved. Open rate is tracked by user and by email campaign. This way you can see exactly who opened a specific email.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
**1. Default (convention over configuration)** - pixel is created with the first available of the following:
|
9
|
+
- *@user* -> *params[:user]* -> *recipient_class.find_by(email: message.to.first)*
|
10
|
+
- *@campaign* -> *params[:campaign]*
|
11
|
+
```ruby
|
12
|
+
class PixelMailer < ApplicationMailer
|
13
|
+
open_tracking_pixel
|
14
|
+
|
15
|
+
def sample
|
16
|
+
mail(to: 'recipient@example.com')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
**2. Explicit use** - pixel is created with the provided data
|
22
|
+
```ruby
|
23
|
+
class PixelMailer < ApplicationMailer
|
24
|
+
open_tracking_pixel recipient: -> { params[:user] },
|
25
|
+
campaign: -> { params[:campaign] }
|
26
|
+
|
27
|
+
def sample
|
28
|
+
mail(to: 'recipient@example.com')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
## Installation
|
34
|
+
Add this line to your application's Gemfile:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
gem 'action_mailer_pixel'
|
38
|
+
```
|
39
|
+
|
40
|
+
And then execute:
|
41
|
+
```bash
|
42
|
+
$ bundle
|
43
|
+
$ bundle exec rails action_mailer_pixel:install:migrations
|
44
|
+
$ bundle exec rails action_mailer_pixel:install
|
45
|
+
```
|
46
|
+
|
47
|
+
## Contributing
|
48
|
+
Contributions are welcome.
|
49
|
+
|
50
|
+
## License
|
51
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'ActionMailerPixel'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActionMailerPixel
|
2
|
+
class PixelController < ApplicationController
|
3
|
+
attr_reader :email_view
|
4
|
+
|
5
|
+
rescue_from ActiveRecord::RecordNotFound, with: :handle_record_not_found
|
6
|
+
|
7
|
+
def open
|
8
|
+
respond_to do |format|
|
9
|
+
format.png do
|
10
|
+
email_view.increment_views!
|
11
|
+
send_data Pixel.file_data,
|
12
|
+
type: Pixel::FILE_TYPE,
|
13
|
+
disposition: 'inline'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def email_view
|
21
|
+
@email_view = EmailView.find_by!(token: params[:token])
|
22
|
+
end
|
23
|
+
|
24
|
+
def handle_record_not_found
|
25
|
+
head :not_found
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ActionMailerPixel
|
2
|
+
class EmailView < ApplicationRecord
|
3
|
+
has_secure_token
|
4
|
+
belongs_to :recipient, class_name: ActionMailerPixel.recipient_class.to_s
|
5
|
+
belongs_to :campaign, class_name: ActionMailerPixel.campaign_class.to_s
|
6
|
+
|
7
|
+
def increment_views!
|
8
|
+
increment!(:views)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module ActionMailerPixel
|
2
|
+
class Pixel
|
3
|
+
BASE64_PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=
|
4
|
+
'.freeze
|
5
|
+
FILE_TYPE = 'image/png'.freeze
|
6
|
+
|
7
|
+
def self.file_data
|
8
|
+
Base64.decode64(BASE64_PNG_PIXEL)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
class CreateEmailViews < ActiveRecord::Migration[5.2]
|
2
|
+
def up
|
3
|
+
create_table :action_mailer_pixel_email_views do |t|
|
4
|
+
t.string :token, unique: true
|
5
|
+
t.integer :recipient_id
|
6
|
+
t.integer :campaign_id
|
7
|
+
t.integer :views, default: 0
|
8
|
+
t.timestamps
|
9
|
+
|
10
|
+
t.index [:recipient_id, :campaign_id],
|
11
|
+
unique: true,
|
12
|
+
name: 'index_email_views_on_recipient_id_and_campaign_id'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def down
|
17
|
+
drop_table :action_mailer_pixel_email_views
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'action_mailer_pixel/engine'
|
2
|
+
|
3
|
+
module ActionMailerPixel
|
4
|
+
class << self
|
5
|
+
attr_accessor :configuration
|
6
|
+
|
7
|
+
def configure
|
8
|
+
@configuration ||= Configuration.new
|
9
|
+
yield @configuration
|
10
|
+
end
|
11
|
+
|
12
|
+
def recipient_class
|
13
|
+
@configuration.recipient_class.constantize
|
14
|
+
end
|
15
|
+
|
16
|
+
def campaign_class
|
17
|
+
@configuration.campaign_class.constantize
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Configuration
|
22
|
+
attr_accessor :recipient_class, :campaign_class
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@recipient_class = 'User'
|
26
|
+
@campaign_class = 'Campaign'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module ActionMailerPixel
|
2
|
+
module DSL
|
3
|
+
module TrackingPixel
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class Configuration
|
7
|
+
def initialize(recipient, campaign)
|
8
|
+
@recipient = recipient
|
9
|
+
@campaign = campaign
|
10
|
+
end
|
11
|
+
|
12
|
+
def recipient
|
13
|
+
@recipient.respond_to?(:call) ? @recipient.call : @recipient
|
14
|
+
end
|
15
|
+
|
16
|
+
def campaign
|
17
|
+
@campaign.respond_to?(:call) ? @campaign.call : @campaign
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class_methods do
|
22
|
+
def open_tracking_pixel(recipient: nil, campaign: nil)
|
23
|
+
define_method :open_pixel_configuration do
|
24
|
+
@open_pixel_configuration ||= Configuration.new(
|
25
|
+
recipient.nil? ? send(:open_pixel_recipient) : recipient,
|
26
|
+
campaign.nil? ? send(:open_pixel_campaign) : campaign
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
define_method :open_pixel_recipient do
|
31
|
+
-> do
|
32
|
+
begin
|
33
|
+
@user ||
|
34
|
+
params[:user] ||
|
35
|
+
ActionMailerPixel.recipient_class.find_by(email: message.to.first)
|
36
|
+
rescue NameError
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
define_method :open_pixel_campaign do
|
43
|
+
-> do
|
44
|
+
begin
|
45
|
+
@campaign || params[:campaign]
|
46
|
+
rescue NameError
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
define_method :append_open_tracking_pixel do
|
53
|
+
interceptor = ActionMailerPixel::Interceptor.new self
|
54
|
+
interceptor.setup_pixel
|
55
|
+
end
|
56
|
+
|
57
|
+
after_action :append_open_tracking_pixel
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'action_mailer_pixel/dsl/tracking_pixel'
|
2
|
+
require 'action_mailer_pixel/interceptor'
|
3
|
+
|
4
|
+
module ActionMailerPixel
|
5
|
+
class Engine < ::Rails::Engine
|
6
|
+
isolate_namespace ActionMailerPixel
|
7
|
+
|
8
|
+
config.generators do |g|
|
9
|
+
g.test_framework :rspec
|
10
|
+
g.fixture_replacement :factory_bot
|
11
|
+
g.factory_bot dir: 'spec/factories'
|
12
|
+
end
|
13
|
+
|
14
|
+
config.to_prepare do
|
15
|
+
::ActionMailer::Base.include ActionMailerPixel::DSL::TrackingPixel
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ActionMailerPixel
|
2
|
+
class Interceptor
|
3
|
+
def initialize(mailer)
|
4
|
+
@mailer = mailer
|
5
|
+
end
|
6
|
+
|
7
|
+
def setup_pixel
|
8
|
+
if html?
|
9
|
+
@email_view = EmailView.create(recipient: recipient, campaign: campaign)
|
10
|
+
|
11
|
+
raw_source = (message.html_part || message).body.raw_source
|
12
|
+
regex = /<\/body>/i
|
13
|
+
|
14
|
+
if raw_source.match(regex)
|
15
|
+
raw_source.gsub!(regex, "#{pixel_html}\\0")
|
16
|
+
else
|
17
|
+
raw_source << pixel_html
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def message
|
25
|
+
@mailer.message
|
26
|
+
end
|
27
|
+
|
28
|
+
def configuration
|
29
|
+
@mailer.open_pixel_configuration
|
30
|
+
end
|
31
|
+
|
32
|
+
def recipient
|
33
|
+
configuration.recipient
|
34
|
+
end
|
35
|
+
|
36
|
+
def campaign
|
37
|
+
configuration.campaign
|
38
|
+
end
|
39
|
+
|
40
|
+
def pixel_url
|
41
|
+
options = (ActionMailer::Base.default_url_options || {})
|
42
|
+
.merge({
|
43
|
+
controller: 'action_mailer_pixel/pixel',
|
44
|
+
action: 'open',
|
45
|
+
token: @email_view.token,
|
46
|
+
format: 'png'
|
47
|
+
})
|
48
|
+
ActionMailerPixel::Engine.routes.url_helpers.url_for(options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def pixel_html
|
52
|
+
ActionController::Base.helpers.image_tag(pixel_url, size: '1x1', alt: '')
|
53
|
+
end
|
54
|
+
|
55
|
+
def html?
|
56
|
+
(message.html_part || message).content_type.include? 'text/html'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActionMailerPixel
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path('../../templates', __FILE__)
|
5
|
+
desc 'Creates ActionMailerPixel initializer for your application'
|
6
|
+
|
7
|
+
def copy_initializer
|
8
|
+
template 'action_mailer_pixel_initializer.rb',
|
9
|
+
'config/initializers/action_mailer_pixel.rb'
|
10
|
+
|
11
|
+
puts 'ActionMailerPixel initializer created successfully.'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
ActionMailerPixel.configure do |config|
|
2
|
+
# Provide a model class name which is going to be used in order to
|
3
|
+
# associate the recipient. Default: 'User'
|
4
|
+
config.recipient_class = 'User'
|
5
|
+
|
6
|
+
# Provide a model class name which is going to be used in order to
|
7
|
+
# associate the specific email delivery. Default: 'Campaign'
|
8
|
+
config.campaign_class = 'Campaign'
|
9
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: action_mailer_pixel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Veselin Stoyanov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-10-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: factory_bot_rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.11'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.11'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.8'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sqlite3
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.3'
|
69
|
+
description: " Tracks email open rate by including a tracking pixel in the message
|
70
|
+
body.\n"
|
71
|
+
email:
|
72
|
+
- stoyanov.veseline@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- MIT-LICENSE
|
78
|
+
- README.md
|
79
|
+
- Rakefile
|
80
|
+
- app/controllers/action_mailer_pixel/application_controller.rb
|
81
|
+
- app/controllers/action_mailer_pixel/pixel_controller.rb
|
82
|
+
- app/models/action_mailer_pixel/application_record.rb
|
83
|
+
- app/models/action_mailer_pixel/email_view.rb
|
84
|
+
- app/models/action_mailer_pixel/pixel.rb
|
85
|
+
- config/routes.rb
|
86
|
+
- db/migrate/20181018105200_create_email_views.rb
|
87
|
+
- lib/action_mailer_pixel.rb
|
88
|
+
- lib/action_mailer_pixel/dsl/tracking_pixel.rb
|
89
|
+
- lib/action_mailer_pixel/engine.rb
|
90
|
+
- lib/action_mailer_pixel/interceptor.rb
|
91
|
+
- lib/action_mailer_pixel/version.rb
|
92
|
+
- lib/generators/action_mailer_pixel/install_generator.rb
|
93
|
+
- lib/generators/templates/action_mailer_pixel_initializer.rb
|
94
|
+
- lib/tasks/action_mailer_pixel_tasks.rake
|
95
|
+
homepage: https://github.com/veskoy/action_mailer_pixel
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 2.7.6
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Simple ActionMailer open tracking pixel.
|
119
|
+
test_files: []
|