mail_spy 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -24,7 +24,7 @@ and AWS settings
24
24
 
25
25
  Then mount the engine in the routes.rb file
26
26
  Rails.application.routes.draw do
27
- mount MailSpy::Engine => "/mail_spy"
27
+ mount MailSpy::Engine => "/mail"
28
28
  end
29
29
 
30
30
  == Usage
@@ -42,7 +42,6 @@ MailSpy.create_email(options)
42
42
 
43
43
  required options include:
44
44
 
45
- * :template_name
46
45
  * :campaign :top level grouping
47
46
  * :stream : mid level grouping
48
47
  * :component : bottom level grouping
@@ -54,6 +53,63 @@ required options include:
54
53
  * one of [:to, :cc, :bcc]
55
54
 
56
55
 
56
+ == Sendgrid
57
+
58
+ MailSpy has bindings baked in for sendgrid event notification.
59
+
60
+ To enable simply configure sendgrid to send post notifications (no batch support yet)
61
+ to YOUR_HOST/mount_path/sendgrid/notification
62
+
63
+ The mount path is usually 'mail' but is configurable in your routes file
64
+
65
+ finally add
66
+
67
+ esp.options = {
68
+ :enable_sendgrid_event_tracking => true
69
+ }
70
+
71
+ to your initializers/mail_spy.rb file
72
+
73
+ This will automatically add in the sendgrid smtp api headers and record all
74
+ events you have setup using the sendgrid admin interface.
75
+
76
+ Reminder: If you are using open and click tracking using MailSpy adding them
77
+ to sendgrid will be redundant and harmful.
78
+
79
+ == Google Analytics
80
+
81
+ MailSpy has automatic bindings for google analytics.
82
+
83
+ MailSpy requires that all emails form into the campaign, stream, component
84
+ structure which lends itself nicely to a direct mapping in google analytics.
85
+
86
+ utm_source => MailSpy - #{tracker_host}
87
+ utm_medium => email
88
+ utm_campaign => campaign
89
+ utm_term => stream
90
+ utm_content => component
91
+
92
+ To enable automatic bindings with all links generated by 'track_link' and
93
+ helper simply add
94
+
95
+ esp.options = {
96
+ :enable_auto_google_analytics => true
97
+ }
98
+
99
+ to your initializers/mail_spy.rb file
100
+
101
+ if you prefer to assign GA settings manually you can do that directly as
102
+ a option to MailSpy.create_email
103
+
104
+ the keys are:
105
+
106
+ * utm_source
107
+ * utm_medium
108
+ * utm_campaign
109
+ * utm_term
110
+ * utm_content
111
+
112
+ Again only links built with track_link will benefit from the GA tokens
57
113
 
58
114
  == Testing
59
115
 
@@ -12,4 +12,6 @@
12
12
  //
13
13
  //= require jquery
14
14
  //= require jquery_ujs
15
+ //= require mail_spy/lib/underscore
16
+ //= require mail_spy/lib/backbone
15
17
  //= require_tree .
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -9,5 +9,4 @@
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 .
13
12
  */
File without changes
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -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 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
@@ -33,20 +33,13 @@ module MailSpy
33
33
  def action
34
34
  email_id = params[:eid]
35
35
  action_type = params[:action_type]
36
- details = params[:details]
36
+ count = params[:count] || 1
37
+ details = params[:details] || {}
37
38
 
38
39
  head :unprocessable_entity and return if email_id.blank? || action_type.blank?
39
40
  head :unprocessable_entity and return if details.present? && !details.kind_of?(Hash)
40
41
 
41
- hash ={}
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
-
42
+ MailSpy.track_action(email_id, action_type, details, count)
50
43
  head 200
51
44
  end
52
45
  end
@@ -21,15 +21,24 @@ module MailSpy
21
21
  tag_options = tag_options(html_options)
22
22
 
23
23
  # Inject our tracking url, and pass in the redirect_url
24
- url = url_for(
24
+ hash = {
25
25
  :controller => "mail_spy/tracking",
26
26
  :action => :link,
27
27
  :url => url,
28
28
  :only_path => false,
29
29
  :host => MailSpy.tracker_host,
30
30
  :n => @_track_count,
31
- :eid => @_email_id
32
- )
31
+ :eid => @_email.id
32
+ }
33
+
34
+ # Add in the GA tokens if present
35
+ hash[:utm_source] = @_email.utm_source if @_email.utm_source.present?
36
+ hash[:utm_medium] = @_email.utm_medium if @_email.utm_medium.present?
37
+ hash[:utm_campaign] = @_email.utm_campaign if @_email.utm_campaign.present?
38
+ hash[:utm_term] = @_email.utm_term if @_email.utm_term.present?
39
+ hash[:utm_content] = @_email.utm_content if @_email.utm_content.present?
40
+
41
+ url = url_for(hash)
33
42
 
34
43
 
35
44
  href_attr = "href=\"#{ERB::Util.html_escape(url)}\"" unless href
@@ -43,7 +52,7 @@ module MailSpy
43
52
  url = url_for(
44
53
  :controller => "mail_spy/tracking",
45
54
  :action => :bug,
46
- :eid => @_email_id,
55
+ :eid => @_email.id,
47
56
  :only_path => false,
48
57
  :host => MailSpy.tracker_host
49
58
  )
@@ -0,0 +1,4 @@
1
+ module MailSpy
2
+ module SendgridHelper
3
+ end
4
+ end
@@ -4,34 +4,28 @@ module MailSpy
4
4
 
5
5
  def template(email)
6
6
  # Slight hack to provide information to helpers
7
- @_email_id = email.id
7
+ @_email = email
8
8
  @template_values = email.template_values
9
+ esp = MailSpy.esps[email.email_service_provider]
9
10
  email_hash = {}
10
11
 
11
-
12
12
  # Evaluate the subject line as erb
13
13
  if email.subject.present?
14
14
  subject = ERB.new(email.subject).result(binding)
15
- email_hash[:subject] = subject
15
+ email_hash[:subject] = subject
16
16
  end
17
17
 
18
18
  # Set Headers
19
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
- headers.merge!(email.headers) if email.headers.present?
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?
22
22
 
23
+ # Create the mail message
23
24
  mail_message = mail(email_hash) do |format|
24
25
  format.text { render :inline => email.text_erb }
25
26
  format.html { render :inline => email.html_erb }
26
27
  end
27
28
 
28
- # Email Service provider setup
29
- # TODO Only support smtp at the moment
30
- raise "No Email service providers installed" unless MailSpy.esps.present?
31
- esp = MailSpy.esps[email.email_service_provider]
32
- esp_key = MailSpy.esps.keys[rand(MailSpy.esps.keys.count)]
33
- esp = MailSpy.esps[esp_key] if esp.blank?
34
-
35
29
  mail_message.delivery_method.settings.merge!(
36
30
  {
37
31
  :address => esp.address,
@@ -13,14 +13,15 @@ module MailSpy
13
13
  field :subject
14
14
  field :sender #Sets envelope from (aka sender header)
15
15
  field :reply_to
16
- field :headers
16
+ field :headers, :type => Hash, :default => {}
17
17
  field :message_id #Sets the unique id for each email
18
18
 
19
19
  # Email content
20
- field :template_values, :type => Hash
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
@@ -34,10 +35,24 @@ module MailSpy
34
35
  field :schedule_at, :type => DateTime
35
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)
@@ -45,11 +60,11 @@ module MailSpy
45
60
 
46
61
  # Great for debugging emails
47
62
  def parsed_html_content
48
- return MailSpy::CoreMailer.template(self).text_part.body.to_s
63
+ return MailSpy::CoreMailer.template(self).html_part.body.to_s
49
64
  end
50
65
 
51
66
  def parsed_text_content
52
- return MailSpy::CoreMailer.template(self).html_part.body.to_s
67
+ return MailSpy::CoreMailer.template(self).text_part.body.to_s
53
68
  end
54
69
 
55
70
  def html_erb
@@ -0,0 +1,48 @@
1
+ <% @campaigns = [{
2
+ :name=>"Valentine's Day",
3
+ :last_sent => Date.today(),
4
+ :sent=>5320
5
+ },{
6
+ :name=>"Mother's Day"
7
+ }] %>
8
+ <%= debug @campaigns %>
9
+ <%# ------------------------------------- Backbone Templates %>
10
+ <script type="text/javascript">
11
+
12
+ Campaign = Backbone.Model.extend({
13
+ initialize: function(){
14
+ alert("Welcome to this world");
15
+ }
16
+ });
17
+ CampaignView = Backbone.View.extend({
18
+ initialize: function(){
19
+ this.render();
20
+ },
21
+ render: function(){
22
+ // Compile the template using underscore
23
+ var template = _.template( $("#campaign_template").html(), {} );
24
+ // Load the compiled HTML into the Backbone "el"
25
+ this.el.html( template );
26
+ }
27
+ });
28
+
29
+ var search_view = new SearchView({ el: $("#search_container") });
30
+ </script>
31
+ <%# ------------------------------------- Backbone Templates %>
32
+ <script type="text/template" id="campaign_template">
33
+ <div class="Campaign">
34
+ <h2><%= c[:name] %></h2>
35
+ </div>
36
+ </script>
37
+ <%# ------------------------------------- Start actual view %>
38
+
39
+ <h1>All Campaigns</h1>
40
+
41
+ <div class="CampaignReport">
42
+ <%# For each campaign %>
43
+ <% @campaigns.each do |c| %>
44
+ <div class="CampaignSummary">
45
+ <h2><%= c[:name] %></h2>
46
+ </div>
47
+ <% end %>
48
+ </div>
data/config/routes.rb CHANGED
@@ -4,4 +4,10 @@ MailSpy::Engine.routes.draw do
4
4
  match "b", :controller => :tracking, :action => :bug
5
5
  match "a", :controller => :tracking, :action => :action
6
6
 
7
+ match "reports", :controller => :reports, :action => :campaigns
8
+ match "email", :controller => :reports, :action => :email
9
+
10
+ # Sendgrid notification api
11
+ post "sendgrid/notification", :controller => :sendgrid, :action => :notification
12
+
7
13
  end
@@ -28,6 +28,7 @@ end
28
28
  # :password,
29
29
  # :domain,
30
30
  # :enable_starttls_auto
31
+ # :options (esp specific options)
31
32
 
32
33
  MailSpy.add_email_service_provider do |esp|
33
34
  esp.name = "sendgrid"
@@ -38,4 +39,8 @@ MailSpy.add_email_service_provider do |esp|
38
39
  esp.password = ENV['SENDGRID_PASSWORD']
39
40
  esp.authentication = :plain
40
41
  esp.enable_starttls_auto = true
42
+ esp.options = {
43
+ :enable_sendgrid_event_tracking => true,
44
+ :enable_auto_google_analytics => true
45
+ }
41
46
  end
@@ -23,15 +23,45 @@ module MailSpy
23
23
  has_sender = options.keys.select { |option| to_options.include? option.intern }.present?
24
24
  raise "Email instance has no sender (to,cc,bcc were all blank)" unless has_sender
25
25
 
26
-
27
- # Make sure that
28
- (required_options + to_options).each do |option|
26
+ # Make sure that all options passed map to a accessor so we don't errantly
27
+ # think we are passing something correctly and really its getting silently
28
+ # ignored
29
+ options.keys.each do |option|
29
30
  unless MailSpy::Email.method_defined? "#{option}=".intern
30
31
  raise "MailSpy::Email doesn't have #{option} as a setter '"
31
32
  end
32
33
  end
33
34
 
35
+ # Ensure that a esp (forced or random) exists for the email
36
+ forced_esp = options[:email_service_provider]
37
+ if forced_esp.present?
38
+ raise "No esp configured with name: #{forced_esp}" if MailSpy.esps[forced_esp].blank?
39
+ else
40
+ raise "No esps configured" if MailSpy.esps.blank?
41
+ esp_key = MailSpy.esps.keys[rand(MailSpy.esps.keys.count)]
42
+ options[:email_service_provider] = esp_key
43
+ end
44
+
45
+ # Google Analytics aupport for automatic population of utm_tokens
46
+ esp = MailSpy.esps[options[:email_service_provider]]
47
+ if esp.options[:enable_auto_google_analytics].present?
48
+ options[:utm_source] ||= 'mailspy'
49
+ options[:utm_medium] ||= 'email'
50
+ options[:utm_campaign] ||= options[:campaign]
51
+ options[:utm_term] ||= options[:stream]
52
+ options[:utm_content] ||= options[:component]
53
+ end
54
+
55
+ #Create the email
34
56
  email = MailSpy::Email.create!(options)
57
+
58
+ #Enable sendgrid specific enhancements
59
+ if esp.options[:enable_sendgrid_event_tracking].present?
60
+ header = MailSpy::Sendgrid::SmtpApiHeader.new
61
+ header.setUniqueArgs({:eid => email.id})
62
+ email.headers = {'X-SMTPAPI' => header.asJSON}.merge(email.headers)
63
+ end
64
+
35
65
  email.save!
36
66
  email
37
67
  end
@@ -52,17 +82,24 @@ module MailSpy
52
82
  pony_hash[pony_key] = value if value.present?
53
83
  end
54
84
 
55
- while (true)
56
- mails = MailSpy::Email.
85
+ while true
86
+ emails = MailSpy::Email.
57
87
  limit(step).offset(offset).
58
- where(:schedule_at.lte => DateTime.now, :sent => false).all
88
+ where(:schedule_at.lte => DateTime.now, :sent => false, :failed => false).all
59
89
  break if mails.blank?
60
- mails.each do |email|
61
- mail = MailSpy::CoreMailer.template(email)
62
- #TODO might be nice to flush mail out in debug mode
63
- mail.deliver
64
- email.update_attribute(:sent, true)
65
- sent += 1
90
+ emails.each do |email|
91
+ begin
92
+ mail = MailSpy::CoreMailer.template(email)
93
+ #TODO might be nice to flush mail out in debug mode
94
+ mail.deliver
95
+ email.update_attribute(:sent, true)
96
+ sent += 1
97
+ rescue Exception => e
98
+ email.failed = true
99
+ email.error_message = e.message
100
+ email.error_backtrace = e.backtrace
101
+ email.save!
102
+ end
66
103
  end
67
104
  offset += step
68
105
  end
@@ -72,10 +109,25 @@ module MailSpy
72
109
 
73
110
 
74
111
  # ------------------------------------------- TRACKING
75
- #
76
-
77
- def track_other_action
78
-
112
+ # MailSpy will automatically track opens and clicks if the tracking bugs
113
+ # and track_link helpers are used. This action allows tracking of
114
+ # arbitrary actions. Please be careful to standardize on action names
115
+ # email_id: The id from the MailSpy::Email record
116
+ # action_type: String denoting the action that occured
117
+ # details: Hash of any details of that action (again strive for standards)
118
+ # count: how many of this action occurred (defaults to 1)
119
+ def track_action(email_id, action_type, details={}, count=1)
120
+ raise "track_action missing email_id" if email_id.blank?
121
+ raise "track_Action missing action_type" if action_type.blank?
122
+
123
+ hash ={}
124
+ hash[:action_type] = action_type
125
+ hash[:count] = count
126
+ hash[:details] = details if details.present? && details.kind_of?(Hash)
127
+
128
+ # Save it up
129
+ email = MailSpy::Email.find(email_id)
130
+ email.actions.create!(hash)
79
131
  end
80
132
 
81
133
  end
@@ -0,0 +1,68 @@
1
+ # Version 1.0
2
+ # Last Updated 6/22/2009
3
+ # Header support for send grids smtp api
4
+ #
5
+ #
6
+ require 'json'
7
+
8
+ module MailSpy
9
+ module Sendgrid
10
+ class SmtpApiHeader
11
+
12
+ def initialize()
13
+ @data = {}
14
+ end
15
+
16
+ def addTo(to)
17
+ @data['to'] ||= []
18
+ @data['to'] += to.kind_of?(Array) ? to : [to]
19
+ end
20
+
21
+ def addSubVal(var, val)
22
+ if not @data['sub']
23
+ @data['sub'] = {}
24
+ end
25
+ if val.instance_of?(Array)
26
+ @data['sub'][var] = val
27
+ else
28
+ @data['sub'][var] = [val]
29
+ end
30
+ end
31
+
32
+ def setUniqueArgs(val)
33
+ if val.instance_of?(Hash)
34
+ @data['unique_args'] = val
35
+ end
36
+ end
37
+
38
+ def setCategory(cat)
39
+ @data['category'] = cat
40
+ end
41
+
42
+ def addFilterSetting(fltr, setting, val)
43
+ if not @data['filters']
44
+ @data['filters'] = {}
45
+ end
46
+ if not @data['filters'][fltr]
47
+ @data['filters'][fltr] = {}
48
+ end
49
+ if not @data['filters'][fltr]['settings']
50
+ @data['filters'][fltr]['settings'] = {}
51
+ end
52
+ @data['filters'][fltr]['settings'][setting] = val
53
+ end
54
+
55
+ def asJSON()
56
+ json = JSON.generate @data
57
+ return json.gsub(/(["\]}])([,:])(["\[{])/, '\\1\\2 \\3')
58
+ end
59
+
60
+ def as_string()
61
+ json = asJSON()
62
+ str = 'X-SMTPAPI: %s' % json.gsub(/(.{1,72})( +|$\n?)|(.{1,72})/, "\\1\\3\n")
63
+ return str
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -1,3 +1,3 @@
1
1
  module MailSpy
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
data/lib/mail_spy.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'pp' #For debugging
2
2
  require 'aws-sdk'
3
+ require 'mail_spy/sendgrid/smtp_api_header'
3
4
  require "mongoid"
4
5
  require "mail_spy/engine"
5
6
  require "mail_spy/manager"
@@ -11,7 +12,7 @@ module MailSpy
11
12
  # Allows the client to configure and add esps to MailSpy
12
13
  MailSpyESP = Struct.new(
13
14
  :address, :port, :authentication, :user_name, :password, :domain,
14
- :enable_starttls_auto, :name
15
+ :enable_starttls_auto, :name, :options
15
16
  )
16
17
  @@esps = {}
17
18
 
@@ -30,7 +31,9 @@ module MailSpy
30
31
  #TODO eventually have this be a view with a interface
31
32
  def self.add_email_service_provider(&block)
32
33
  esp = MailSpyESP.new
34
+ esp.options = {} # Important! provides default options list for internals
33
35
  block.call(esp)
36
+ esp.options.to_options!
34
37
  @@esps[esp.name] = esp
35
38
  end
36
39
 
@@ -35,4 +35,8 @@ MailSpy.add_email_service_provider do |esp|
35
35
  esp.password = "qb6qnim0"
36
36
  esp.authentication = :plain
37
37
  esp.enable_starttls_auto = true
38
+ esp.options = {
39
+ :enable_sendgrid_event_tracking => true,
40
+ :enable_auto_google_analytics => true
41
+ }
38
42
  end
@@ -1,3 +1,3 @@
1
1
  Rails.application.routes.draw do
2
- mount MailSpy::Engine => "/mail_spy"
2
+ mount MailSpy::Engine => "/mail"
3
3
  end