mail_spy 0.0.2 → 0.1.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/README.rdoc +86 -24
- data/app/assets/images/mail_spy/background_tile.gif +0 -0
- data/app/assets/images/mail_spy/mailspy_sprite.png +0 -0
- data/app/assets/images/mail_spy/spy.ico +0 -0
- data/app/assets/javascripts/mail_spy/lib/backbone.js +1256 -0
- data/app/assets/javascripts/mail_spy/lib/underscore.js +999 -0
- data/app/assets/javascripts/mail_spy/reports.js +2 -0
- data/app/assets/javascripts/mail_spy/sendgrid.js +2 -0
- data/app/assets/stylesheets/mail_spy/application.css +62 -1
- data/app/assets/stylesheets/mail_spy/reports.css.scss +128 -0
- data/app/assets/stylesheets/mail_spy/sendgrid.css +4 -0
- data/app/controllers/mail_spy/reports_controller.rb +29 -0
- data/app/controllers/mail_spy/sendgrid_controller.rb +26 -0
- data/app/controllers/mail_spy/tracking_controller.rb +19 -11
- data/app/helpers/mail_spy/email_helper.rb +21 -3
- data/app/helpers/mail_spy/reports_helper.rb +4 -0
- data/app/helpers/mail_spy/sendgrid_helper.rb +4 -0
- data/app/mailers/mail_spy/core_mailer.rb +15 -18
- data/app/models/mail_spy/email.rb +127 -19
- data/app/models/mail_spy/process_log.rb +25 -0
- data/app/views/layouts/mail_spy/application.html.erb +3 -0
- data/app/views/mail_spy/reports/campaigns.html.erb +138 -0
- data/config/routes.rb +6 -0
- data/lib/generators/mail_spy/templates/mail_spy.rb +25 -1
- data/lib/mail_spy/manager.rb +100 -54
- data/lib/mail_spy/sendgrid/smtp_api_header.rb +68 -0
- data/lib/mail_spy/version.rb +1 -1
- data/lib/mail_spy.rb +39 -5
- data/test/dummy/app/views/email_templates/helper_test.html.erb +15 -0
- data/test/dummy/app/views/email_templates/helper_test.text.erb +11 -0
- data/test/dummy/config/environments/development.rb +1 -1
- data/test/dummy/config/environments/production.rb +5 -1
- data/test/dummy/config/environments/test.rb +4 -1
- data/test/dummy/config/initializers/mail_spy.rb +42 -0
- data/test/dummy/config/routes.rb +1 -2
- data/test/dummy/db/schema.rb +16 -0
- data/test/dummy/db/seeds.rb +16 -13
- data/test/dummy/lib/tasks/emails.rake +8 -0
- data/test/dummy/log/development.log +782 -0
- data/test/dummy/log/test.log +9952 -0
- data/test/fixtures/mail_spy/process_logs.yml +15 -0
- data/test/functional/mail_spy/core_mailer_test.rb +16 -9
- data/test/functional/mail_spy/reports_controller_test.rb +9 -0
- data/test/functional/mail_spy/sendgrid_controller_test.rb +9 -0
- data/test/functional/mail_spy/tracking_controller_test.rb +16 -11
- data/test/test_email_credentials.yml +2 -2
- data/test/test_email_credentials.yml.sample +0 -8
- data/test/test_helper.rb +15 -41
- data/test/unit/helpers/mail_spy/reports_helper_test.rb +6 -0
- data/test/unit/helpers/mail_spy/sendgrid_helper_test.rb +6 -0
- data/test/unit/mail_spy/manager_test.rb +101 -122
- data/test/unit/mail_spy/{email_template_test.rb → process_log_test.rb} +1 -1
- metadata +122 -34
- data/app/models/mail_spy/email_template.rb +0 -17
- data/test/fixtures/mail_spy/email_templates.yml +0 -9
- /data/lib/generators/mail_spy/{initialize_generator.rb → initializer_generator.rb} +0 -0
|
@@ -9,5 +9,66 @@
|
|
|
9
9
|
* compiled file, but it's generally better to create a new file per style scope.
|
|
10
10
|
*
|
|
11
11
|
*= require_self
|
|
12
|
-
*= require_tree
|
|
12
|
+
*= require_tree
|
|
13
13
|
*/
|
|
14
|
+
|
|
15
|
+
/*************************************** Reset Styles */
|
|
16
|
+
|
|
17
|
+
html, body, div, span, applet, object, iframe,
|
|
18
|
+
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
|
19
|
+
a, abbr, acronym, address, big, cite, code,
|
|
20
|
+
del, dfn, em, img, ins, kbd, q, s, samp,
|
|
21
|
+
small, strike, strong, sub, sup, tt, var,
|
|
22
|
+
b, u, i, center,
|
|
23
|
+
dl, dt, dd, ol, ul, li,
|
|
24
|
+
fieldset, form, label, legend,
|
|
25
|
+
table, caption, tbody, tfoot, thead, tr, th, td,
|
|
26
|
+
article, aside, canvas, details, embed,
|
|
27
|
+
figure, figcaption, footer, header, hgroup,
|
|
28
|
+
menu, nav, output, ruby, section, summary,
|
|
29
|
+
time, mark, audio, video {
|
|
30
|
+
margin: 0;
|
|
31
|
+
padding: 0;
|
|
32
|
+
border: 0;
|
|
33
|
+
font-size: 100%;
|
|
34
|
+
font: inherit;
|
|
35
|
+
vertical-align: baseline;
|
|
36
|
+
}
|
|
37
|
+
/* HTML5 display-role reset for older browsers */
|
|
38
|
+
article, aside, details, figcaption, figure,
|
|
39
|
+
footer, header, hgroup, menu, nav, section {
|
|
40
|
+
display: block;
|
|
41
|
+
}
|
|
42
|
+
body {
|
|
43
|
+
line-height: 1;
|
|
44
|
+
}
|
|
45
|
+
ol, ul {
|
|
46
|
+
list-style: none;
|
|
47
|
+
}
|
|
48
|
+
blockquote, q {
|
|
49
|
+
quotes: none;
|
|
50
|
+
}
|
|
51
|
+
blockquote:before, blockquote:after,
|
|
52
|
+
q:before, q:after {
|
|
53
|
+
content: '';
|
|
54
|
+
content: none;
|
|
55
|
+
}
|
|
56
|
+
table {
|
|
57
|
+
border-collapse: collapse;
|
|
58
|
+
border-spacing: 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/*************************************** End Reset */
|
|
62
|
+
/*************************************** New Global Styles */
|
|
63
|
+
|
|
64
|
+
body{
|
|
65
|
+
font-family: 'Spinnaker', sans-serif;
|
|
66
|
+
color:#fff;
|
|
67
|
+
background-image: url(background_tile.gif);
|
|
68
|
+
}
|
|
69
|
+
h1{
|
|
70
|
+
font-size:24px;
|
|
71
|
+
}
|
|
72
|
+
.Deemph{
|
|
73
|
+
font-size:.72em;
|
|
74
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Place all the styles related to the matching controller here.
|
|
3
|
+
They will automatically be included in application.css.
|
|
4
|
+
*/
|
|
5
|
+
$colorHighlight:#80ff7a;
|
|
6
|
+
$colorMedlight:#214d30;
|
|
7
|
+
$colorSemiLowlight:#c3c3c3;
|
|
8
|
+
$colorLowlight:#3e4041;
|
|
9
|
+
$colorHighlightBad:#62222a;
|
|
10
|
+
$colorMedlightBad:#663137;
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
._active{
|
|
15
|
+
color:$colorHighlight;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**************************** Sprites for mailspy_sprite.png */
|
|
19
|
+
.MsSprite{
|
|
20
|
+
background-image:url(mailspy_sprite.png);
|
|
21
|
+
background-repeat:no-repeat;
|
|
22
|
+
display:inline-block;
|
|
23
|
+
*display:inline; zoom:1;
|
|
24
|
+
vertical-align:middle;
|
|
25
|
+
text-indent:-9999px;
|
|
26
|
+
|
|
27
|
+
&.Logo{
|
|
28
|
+
background-position:0 0;
|
|
29
|
+
width:180px;
|
|
30
|
+
height:50px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&.Nav{
|
|
34
|
+
width: 27px;
|
|
35
|
+
height: 34px;
|
|
36
|
+
background-position-x:-50px;
|
|
37
|
+
|
|
38
|
+
&.Campaign{
|
|
39
|
+
background-position-y:-100px;
|
|
40
|
+
}
|
|
41
|
+
&.History{
|
|
42
|
+
background-position-y:-150px;
|
|
43
|
+
}
|
|
44
|
+
&.Template{
|
|
45
|
+
background-position-y:-200px;
|
|
46
|
+
}
|
|
47
|
+
&.Menu{
|
|
48
|
+
background-position-y:-250px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&._active{
|
|
52
|
+
background-position-x:0;
|
|
53
|
+
}
|
|
54
|
+
&:hover{
|
|
55
|
+
background-position-x:-100px;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&.People{
|
|
60
|
+
background-position: -200px 0px;
|
|
61
|
+
height:60px;
|
|
62
|
+
width:80px;
|
|
63
|
+
}
|
|
64
|
+
&.Mail{
|
|
65
|
+
background-position: -200px -100px;
|
|
66
|
+
height:60px;
|
|
67
|
+
width:80px;
|
|
68
|
+
}
|
|
69
|
+
&.Pointer{
|
|
70
|
+
background-position: -200px -200px;
|
|
71
|
+
height:60px;
|
|
72
|
+
width:80px;
|
|
73
|
+
}
|
|
74
|
+
&.Money{
|
|
75
|
+
background-position: -200px -300px;
|
|
76
|
+
height:60px;
|
|
77
|
+
width:80px;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**************************** End mailspy_sprite.png*/
|
|
82
|
+
/**************************** Report Components */
|
|
83
|
+
.KpiContainer{
|
|
84
|
+
margin:1em;
|
|
85
|
+
}
|
|
86
|
+
.Kpi{
|
|
87
|
+
display:inline-block;
|
|
88
|
+
*display:inline; zoom:1;
|
|
89
|
+
width:200px;
|
|
90
|
+
text-align:center;
|
|
91
|
+
|
|
92
|
+
.Icon{
|
|
93
|
+
display:block;
|
|
94
|
+
margin:8px auto;
|
|
95
|
+
}
|
|
96
|
+
kpi{
|
|
97
|
+
color:$colorMedlight;
|
|
98
|
+
font-size:1.2em;
|
|
99
|
+
margin:.2em;
|
|
100
|
+
}
|
|
101
|
+
figure{
|
|
102
|
+
color:$colorHighlight;
|
|
103
|
+
font-size:1.8em;
|
|
104
|
+
margin:.2em;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.CampaignContainer{
|
|
109
|
+
background-color:rgba(0,0,0,.4);
|
|
110
|
+
padding:.2em 1em 1em 1em;
|
|
111
|
+
margin:2em 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.FunnelContainer{
|
|
115
|
+
padding:.2em 1em 1em 1em;
|
|
116
|
+
margin:2em 0;
|
|
117
|
+
thead{
|
|
118
|
+
td{
|
|
119
|
+
color:$colorLowlight;
|
|
120
|
+
text-align:left;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
td{
|
|
124
|
+
padding:.5em 2em;
|
|
125
|
+
color:$colorHighlight;
|
|
126
|
+
text-align:left;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module MailSpy
|
|
2
|
+
class ReportsController < ApplicationController
|
|
3
|
+
|
|
4
|
+
def campaigns
|
|
5
|
+
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def emails
|
|
9
|
+
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def email
|
|
13
|
+
@email = MailSpy.create_email({
|
|
14
|
+
:campaign => "2012-valentines",
|
|
15
|
+
:stream => "a-yikes",
|
|
16
|
+
:component => "a",
|
|
17
|
+
:schedule_at => Date.today()+1, # Should never be used
|
|
18
|
+
:subject => "Just a test",
|
|
19
|
+
:template_values => {},
|
|
20
|
+
:from => "EMAIL PREVIEW",
|
|
21
|
+
:reply_to => "NOREPLY",
|
|
22
|
+
:to => "nowhere"
|
|
23
|
+
})
|
|
24
|
+
@email[:cache_bust] = true
|
|
25
|
+
render :text=>@email.parsed_html_content, :html_safe => true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module MailSpy
|
|
2
|
+
class SendgridController < ApplicationController
|
|
3
|
+
|
|
4
|
+
#Parses a notification from the sendgrid system
|
|
5
|
+
def notification
|
|
6
|
+
#Must be passed in via SendGrids SMTP API Headers
|
|
7
|
+
email_id = params['eid']
|
|
8
|
+
return head 400 if email_id.blank? || params[:event].blank?
|
|
9
|
+
|
|
10
|
+
#Send grid event name
|
|
11
|
+
event_action = "sendgrid-#{params[:event]}"
|
|
12
|
+
|
|
13
|
+
case params[:event]
|
|
14
|
+
when 'bounce', 'dropped'
|
|
15
|
+
MailSpy.track_action(email_id, event_action, {:reason => params[:reason]})
|
|
16
|
+
when 'click'
|
|
17
|
+
MailSpy.track_action(email_id, event_action, {:url => params[:url]})
|
|
18
|
+
when 'deferred', 'delivered', 'processed', 'open', 'spamreport', 'unsubscribe'
|
|
19
|
+
MailSpy.track_action(email_id, event_action)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
head :ok
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -14,7 +14,22 @@ module MailSpy
|
|
|
14
14
|
}
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
# Add in the GA tokens if present
|
|
19
|
+
|
|
20
|
+
ga_hash = {}
|
|
21
|
+
ga_hash[:utm_source] = email.utm_source if email.utm_source.present?
|
|
22
|
+
ga_hash[:utm_medium] = email.utm_medium if email.utm_medium.present?
|
|
23
|
+
ga_hash[:utm_campaign] = email.utm_campaign if email.utm_campaign.present?
|
|
24
|
+
ga_hash[:utm_term] = email.utm_term if email.utm_term.present?
|
|
25
|
+
ga_hash[:utm_content] = email.utm_content if email.utm_content.present?
|
|
26
|
+
|
|
27
|
+
#TODO this is not fool proof, it breaks on bad urls (like missing http://)
|
|
28
|
+
#TODO this also breaks when a param is already present it isn;t smart
|
|
29
|
+
uri = URI.parse(params[:url])
|
|
30
|
+
uri.query = [uri.query, ga_hash.to_param].compact.join('&')
|
|
31
|
+
|
|
32
|
+
redirect_to uri.to_s
|
|
18
33
|
end
|
|
19
34
|
|
|
20
35
|
def bug
|
|
@@ -33,20 +48,13 @@ module MailSpy
|
|
|
33
48
|
def action
|
|
34
49
|
email_id = params[:eid]
|
|
35
50
|
action_type = params[:action_type]
|
|
36
|
-
|
|
51
|
+
count = params[:count] || 1
|
|
52
|
+
details = params[:details] || {}
|
|
37
53
|
|
|
38
54
|
head :unprocessable_entity and return if email_id.blank? || action_type.blank?
|
|
39
55
|
head :unprocessable_entity and return if details.present? && !details.kind_of?(Hash)
|
|
40
56
|
|
|
41
|
-
|
|
42
|
-
hash[:action_type] = action_type
|
|
43
|
-
hash[:count] = params[:count] if !params[:count].nil?
|
|
44
|
-
hash[:details] = details if details.present? && details.kind_of?(Hash)
|
|
45
|
-
|
|
46
|
-
# Save it up
|
|
47
|
-
email = MailSpy::Email.find(email_id)
|
|
48
|
-
email.actions.create!(hash)
|
|
49
|
-
|
|
57
|
+
MailSpy.track_action(email_id, action_type, details, count)
|
|
50
58
|
head 200
|
|
51
59
|
end
|
|
52
60
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
1
3
|
module MailSpy
|
|
2
4
|
module EmailHelper
|
|
3
5
|
|
|
@@ -21,9 +23,18 @@ module MailSpy
|
|
|
21
23
|
tag_options = tag_options(html_options)
|
|
22
24
|
|
|
23
25
|
# Inject our tracking url, and pass in the redirect_url
|
|
24
|
-
url = url_for(:controller => "mail_spy/tracking", :action => :link, :url => url,
|
|
25
|
-
:n => @_track_count, :eid => @_email_id)
|
|
26
26
|
|
|
27
|
+
hash = {
|
|
28
|
+
:controller => "mail_spy/tracking",
|
|
29
|
+
:action => :link,
|
|
30
|
+
:url => url,
|
|
31
|
+
:only_path => false,
|
|
32
|
+
:host => MailSpy.tracker_host,
|
|
33
|
+
:n => @_track_count,
|
|
34
|
+
:eid => @_email.id
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
url = url_for(hash)
|
|
27
38
|
|
|
28
39
|
href_attr = "href=\"#{ERB::Util.html_escape(url)}\"" unless href
|
|
29
40
|
"<a #{href_attr}#{tag_options}>#{ERB::Util.html_escape(name || url)}</a>".html_safe
|
|
@@ -33,7 +44,14 @@ module MailSpy
|
|
|
33
44
|
|
|
34
45
|
# Support for open tracking, client support, etc
|
|
35
46
|
def tracking_bug
|
|
36
|
-
|
|
47
|
+
url = url_for(
|
|
48
|
+
:controller => "mail_spy/tracking",
|
|
49
|
+
:action => :bug,
|
|
50
|
+
:eid => @_email.id,
|
|
51
|
+
:only_path => false,
|
|
52
|
+
:host => MailSpy.tracker_host
|
|
53
|
+
)
|
|
54
|
+
"<img src='#{url}' style='display:none' width='1' height='1' border='0' />".html_safe
|
|
37
55
|
end
|
|
38
56
|
|
|
39
57
|
end
|
|
@@ -4,31 +4,28 @@ module MailSpy
|
|
|
4
4
|
|
|
5
5
|
def template(email)
|
|
6
6
|
# Slight hack to provide information to helpers
|
|
7
|
-
@
|
|
7
|
+
@_email = email
|
|
8
8
|
@template_values = email.template_values
|
|
9
|
-
|
|
10
|
-
# Set Headers
|
|
9
|
+
esp = MailSpy.esps[email.email_service_provider]
|
|
11
10
|
email_hash = {}
|
|
12
|
-
std_email_keys = [:to, :cc, :bcc, :from, :subject, :message_id, :sender, :reply_to]
|
|
13
|
-
std_email_keys.each { |key| set_if_present(email_hash,email, key) }
|
|
14
|
-
headers.merge!(email.headers) if email.headers.present?
|
|
15
11
|
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
# Evaluate the subject line as erb
|
|
13
|
+
if email.subject.present?
|
|
14
|
+
subject = ERB.new(email.subject).result(binding)
|
|
15
|
+
email_hash[:subject] = subject
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Set Headers
|
|
19
|
+
std_email_keys = [:to, :cc, :bcc, :from, :message_id, :sender, :reply_to]
|
|
20
|
+
std_email_keys.each { |key| set_if_present(email_hash, email, key) }
|
|
21
|
+
email.headers.each {|key, value| headers[key] = value} if email.headers.present?
|
|
19
22
|
|
|
23
|
+
# Create the mail message
|
|
20
24
|
mail_message = mail(email_hash) do |format|
|
|
21
|
-
format.text { render :inline => text_erb }
|
|
22
|
-
format.html { render :inline => html_erb }
|
|
25
|
+
format.text { render :inline => email.text_erb }
|
|
26
|
+
format.html { render :inline => email.html_erb }
|
|
23
27
|
end
|
|
24
28
|
|
|
25
|
-
# Email Service provider setup
|
|
26
|
-
# TODO Only support smtp at the moment
|
|
27
|
-
raise "No Email service providers installed" unless MailSpy.esps.present?
|
|
28
|
-
esp = MailSpy.esps[email.email_service_provider]
|
|
29
|
-
esp_key = MailSpy.esps.keys[rand(MailSpy.esps.keys.count)]
|
|
30
|
-
esp = MailSpy.esps[esp_key] if esp.blank?
|
|
31
|
-
|
|
32
29
|
mail_message.delivery_method.settings.merge!(
|
|
33
30
|
{
|
|
34
31
|
:address => esp.address,
|
|
@@ -3,6 +3,8 @@ module MailSpy
|
|
|
3
3
|
include Mongoid::Document
|
|
4
4
|
include Mongoid::Timestamps
|
|
5
5
|
|
|
6
|
+
@@template_cache = {}
|
|
7
|
+
|
|
6
8
|
# Standard Email options
|
|
7
9
|
field :to
|
|
8
10
|
field :cc
|
|
@@ -11,16 +13,15 @@ module MailSpy
|
|
|
11
13
|
field :subject
|
|
12
14
|
field :sender #Sets envelope from (aka sender header)
|
|
13
15
|
field :reply_to
|
|
14
|
-
field :headers
|
|
16
|
+
field :headers, :type => Hash, :default => {}
|
|
15
17
|
field :message_id #Sets the unique id for each email
|
|
16
18
|
|
|
17
19
|
# Email content
|
|
18
|
-
field :
|
|
19
|
-
field :template_values, :type => Hash
|
|
20
|
-
belongs_to :email_template, :class_name => 'MailSpy::EmailTemplate'
|
|
20
|
+
field :template_values, :type => Hash, :default => {}
|
|
21
21
|
|
|
22
22
|
# Support for tracking which esp ran the email
|
|
23
23
|
field :email_service_provider, :type => String
|
|
24
|
+
validates_presence_of :email_service_provider
|
|
24
25
|
|
|
25
26
|
# References back to a user in another db
|
|
26
27
|
field :user_id, :type => Integer
|
|
@@ -32,39 +33,146 @@ module MailSpy
|
|
|
32
33
|
|
|
33
34
|
# Scheduling and completion
|
|
34
35
|
field :schedule_at, :type => DateTime
|
|
35
|
-
field :sent, :type => Boolean
|
|
36
|
+
field :sent, :type => Boolean, :default => false
|
|
36
37
|
index :schedule_at
|
|
38
|
+
index :sent
|
|
39
|
+
|
|
40
|
+
# Error reporting
|
|
41
|
+
field :failed, :type => Boolean, :default => false
|
|
42
|
+
field :error_message, :type => String
|
|
43
|
+
field :error_backtrace, :type => Array
|
|
44
|
+
index :failed
|
|
37
45
|
|
|
38
46
|
# Record keeping of what happened
|
|
39
47
|
embeds_many :actions, :class_name => "MailSpy::Action"
|
|
40
48
|
|
|
49
|
+
# Google Analytics tracking
|
|
50
|
+
field :utm_source, :type => String
|
|
51
|
+
field :utm_medium, :type => String
|
|
52
|
+
field :utm_campaign, :type => String
|
|
53
|
+
field :utm_term, :type => String
|
|
54
|
+
field :utm_content, :type => String
|
|
55
|
+
|
|
41
56
|
|
|
42
57
|
def template_values
|
|
43
58
|
self.read_attribute(:template_values).try(:with_indifferent_access)
|
|
44
59
|
end
|
|
45
60
|
|
|
46
|
-
|
|
47
|
-
def email_parts
|
|
48
|
-
template = self.email_template
|
|
49
|
-
html_erb = template.html_erb
|
|
50
|
-
text_erb = template.text_erb
|
|
51
|
-
mail = MailSpy::DummyMailer.template(html_erb, text_erb, self.template_values)
|
|
52
|
-
return {
|
|
53
|
-
:html => mail.html_part.body.to_s,
|
|
54
|
-
:text => mail.text_part.body.to_s
|
|
55
|
-
}
|
|
56
|
-
end
|
|
57
|
-
|
|
58
61
|
# Great for debugging emails
|
|
59
62
|
def parsed_html_content
|
|
60
|
-
return MailSpy::CoreMailer.template(self).
|
|
63
|
+
return MailSpy::CoreMailer.template(self).html_part.body.to_s
|
|
61
64
|
end
|
|
62
65
|
|
|
63
66
|
def parsed_text_content
|
|
64
|
-
return MailSpy::CoreMailer.template(self).
|
|
67
|
+
return MailSpy::CoreMailer.template(self).text_part.body.to_s
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def html_erb
|
|
71
|
+
load_template("html.erb")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def text_erb
|
|
75
|
+
load_template("text.erb")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.deliver_batch(current_time, start_id, offset, total, step=200, num_threads=100)
|
|
79
|
+
wq = WorkQueue.new(num_threads, step*2)
|
|
80
|
+
processed = 0
|
|
81
|
+
while true
|
|
82
|
+
emails = self.where(
|
|
83
|
+
:schedule_at.lte => current_time,
|
|
84
|
+
:sent => false,
|
|
85
|
+
:failed => false,
|
|
86
|
+
:_id.gt => start_id
|
|
87
|
+
).limit(step).offset(offset)
|
|
88
|
+
|
|
89
|
+
break if total >= processed || emails.blank?
|
|
90
|
+
emails.each do |email|
|
|
91
|
+
wq.enqueue_b do
|
|
92
|
+
email.deliver
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
processed += emails.count
|
|
96
|
+
end
|
|
97
|
+
wq.join
|
|
98
|
+
processed
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def deliver
|
|
102
|
+
begin
|
|
103
|
+
MailSpy::CoreMailer.template(self).deliver
|
|
104
|
+
self.update_attribute(:sent, true)
|
|
105
|
+
rescue Exception => e
|
|
106
|
+
self.failed = true
|
|
107
|
+
self.error_message = e.try(:message)
|
|
108
|
+
self.error_backtrace = e.try(:backtrace)
|
|
109
|
+
self.save!
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
protected
|
|
115
|
+
|
|
116
|
+
# Loads a template file from s3 using
|
|
117
|
+
def load_template(suffix)
|
|
118
|
+
raise "No aws_campaign_bucket_set" if MailSpy.aws_campaign_bucket.strip.blank?
|
|
119
|
+
|
|
120
|
+
# Check the cache using our convention
|
|
121
|
+
path = "#{self.campaign}/#{self.stream}/#{self.component}.#{suffix}"
|
|
122
|
+
return @@template_cache[path] if @@template_cache[path].present? && !self[:cache_bust]
|
|
123
|
+
|
|
124
|
+
# Load the object from s3
|
|
125
|
+
s3 = AWS::S3.new(:access_key_id => MailSpy.aws_access_key_id,
|
|
126
|
+
:secret_access_key => MailSpy.aws_secret_access_key)
|
|
127
|
+
campaign_bucket = s3.buckets[MailSpy.aws_campaign_bucket]
|
|
128
|
+
campaign_bucket = buckets.create(MailSpy.aws_campaign_bucket) unless campaign_bucket.exists?
|
|
129
|
+
object = campaign_bucket.objects[path]
|
|
130
|
+
raise "no object found at path: #{path}" unless object.exists?
|
|
131
|
+
|
|
132
|
+
# Read and return
|
|
133
|
+
@@template_cache[path] = (object.read || "")
|
|
134
|
+
return @@template_cache[path]
|
|
65
135
|
end
|
|
66
136
|
|
|
67
137
|
|
|
138
|
+
def self.generate_subject_reports
|
|
139
|
+
map = <<-eof
|
|
140
|
+
function(){
|
|
141
|
+
var action_counts = {};
|
|
142
|
+
for(var i=0, len = this.actions.length; i < len; i++){
|
|
143
|
+
var action = this.actions[i];
|
|
144
|
+
action_counts[action.action_type] = action_counts[action.action_type] || 0;
|
|
145
|
+
action_counts[action.action_type]++;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
emit({
|
|
149
|
+
campaign:this.campaign,
|
|
150
|
+
stream:this.stream,
|
|
151
|
+
component:this.component,
|
|
152
|
+
subject:this.subject
|
|
153
|
+
}, action_counts);
|
|
154
|
+
}
|
|
155
|
+
eof
|
|
156
|
+
|
|
157
|
+
reduce = <<-eof
|
|
158
|
+
function(key, values){
|
|
159
|
+
var result = {};
|
|
160
|
+
|
|
161
|
+
values.forEach(function(hash){
|
|
162
|
+
for(var key in hash){
|
|
163
|
+
result[key] = result[key] || 0
|
|
164
|
+
result[key] += hash[key]
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
eof
|
|
171
|
+
|
|
172
|
+
results = collection.map_reduce(map, reduce, :out => "test_reports")
|
|
173
|
+
|
|
174
|
+
end
|
|
175
|
+
|
|
68
176
|
def self.generate_campaign_stats
|
|
69
177
|
|
|
70
178
|
map = <<-eof
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module MailSpy
|
|
2
|
+
class ProcessLog
|
|
3
|
+
include Mongoid::Document
|
|
4
|
+
include Mongoid::Timestamps
|
|
5
|
+
|
|
6
|
+
field :start, :type => Time
|
|
7
|
+
field :end, :type => Time
|
|
8
|
+
field :seconds_elapsed, :type => Integer
|
|
9
|
+
field :success, :type => Boolean, :default => false
|
|
10
|
+
field :running, :type => Boolean, :default => false
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def self.currently_processing?
|
|
14
|
+
self.where(:running => true).first.present?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Deletes logs after a given date if no date is given we default
|
|
18
|
+
# to logs that are older that 3 months.
|
|
19
|
+
def self.delete_stale_logs(date=nil)
|
|
20
|
+
date = Time.now.advance(:months => -3) if date.nil?
|
|
21
|
+
self.where("created_at < #{date}").delete_all
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<title>MailSpy</title>
|
|
5
|
+
<link rel="icon" href="/assets/mail_spy/spy.ico" type="image/x-icon">
|
|
6
|
+
<link rel="shortcut icon" href="/assets/mail_spy/spy.ico" type="image/x-icon">
|
|
5
7
|
<%= stylesheet_link_tag "mail_spy/application", :media => "all" %>
|
|
6
8
|
<%= javascript_include_tag "mail_spy/application" %>
|
|
9
|
+
<link href='http://fonts.googleapis.com/css?family=Spinnaker' rel='stylesheet' type='text/css'>
|
|
7
10
|
<%= csrf_meta_tags %>
|
|
8
11
|
</head>
|
|
9
12
|
<body>
|