mail_spy 0.0.5 → 0.0.6

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 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