mail_spy 0.0.2
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/MIT-LICENSE +20 -0
- data/README.rdoc +68 -0
- data/Rakefile +40 -0
- data/app/assets/javascripts/mail_spy/application.js +15 -0
- data/app/assets/javascripts/mail_spy/tracking.js +2 -0
- data/app/assets/stylesheets/mail_spy/application.css +13 -0
- data/app/assets/stylesheets/mail_spy/tracking.css +4 -0
- data/app/controllers/mail_spy/application_controller.rb +4 -0
- data/app/controllers/mail_spy/tracking_controller.rb +53 -0
- data/app/helpers/mail_spy/application_helper.rb +4 -0
- data/app/helpers/mail_spy/email_helper.rb +40 -0
- data/app/mailers/mail_spy/core_mailer.rb +55 -0
- data/app/models/mail_spy/action.rb +23 -0
- data/app/models/mail_spy/campaign_report.rb +6 -0
- data/app/models/mail_spy/component_report.rb +6 -0
- data/app/models/mail_spy/email.rb +93 -0
- data/app/models/mail_spy/email_template.rb +17 -0
- data/app/models/mail_spy/stream_report.rb +6 -0
- data/app/views/layouts/mail_spy/application.html.erb +14 -0
- data/config/routes.rb +7 -0
- data/lib/generators/mail_spy/initialize_generator.rb +12 -0
- data/lib/generators/mail_spy/templates/mail_spy.rb +25 -0
- data/lib/mail_spy/engine.rb +13 -0
- data/lib/mail_spy/manager.rb +97 -0
- data/lib/mail_spy/version.rb +3 -0
- data/lib/mail_spy.rb +36 -0
- data/lib/tasks/mail_spy_tasks.rake +40 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config/application.rb +56 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/mongoid.yml +20 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/seeds.rb +16 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +212 -0
- data/test/dummy/log/test.log +6841 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/fixtures/mail_spy/actions.yml +7 -0
- data/test/fixtures/mail_spy/campaign_reports.yml +11 -0
- data/test/fixtures/mail_spy/component_reports.yml +11 -0
- data/test/fixtures/mail_spy/email_templates.yml +9 -0
- data/test/fixtures/mail_spy/emails.yml +15 -0
- data/test/fixtures/mail_spy/stream_reports.yml +11 -0
- data/test/functional/mail_spy/core_mailer_test.rb +33 -0
- data/test/functional/mail_spy/tracking_controller_test.rb +83 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/mail_spy_test.rb +7 -0
- data/test/test_email_credentials.yml +9 -0
- data/test/test_email_credentials.yml.sample +9 -0
- data/test/test_helper.rb +107 -0
- data/test/unit/helpers/mail_spy/tracking_helper_test.rb +6 -0
- data/test/unit/mail_spy/action_test.rb +9 -0
- data/test/unit/mail_spy/campaign_report_test.rb +9 -0
- data/test/unit/mail_spy/component_report_test.rb +9 -0
- data/test/unit/mail_spy/email_template_test.rb +9 -0
- data/test/unit/mail_spy/email_test.rb +9 -0
- data/test/unit/mail_spy/manager_test.rb +164 -0
- data/test/unit/mail_spy/stream_report_test.rb +9 -0
- metadata +343 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
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,68 @@
|
|
1
|
+
= Mailspy
|
2
|
+
|
3
|
+
ESP agnostic sending and tracking platform for email campaigns
|
4
|
+
|
5
|
+
== Requirements
|
6
|
+
|
7
|
+
Mongodb > 1.8
|
8
|
+
|
9
|
+
== Setup
|
10
|
+
|
11
|
+
Add gem "mailspy" to your Gemfile
|
12
|
+
gem "mailspy"
|
13
|
+
|
14
|
+
|
15
|
+
Create the mongoid configuration for the mongo db
|
16
|
+
|
17
|
+
rails g mailspy:initialize
|
18
|
+
|
19
|
+
Fill out the generated mailspy_mongoid.yml with the mongodb configuration settings
|
20
|
+
|
21
|
+
Then mount the engine in the routes.rb file
|
22
|
+
Rails.application.routes.draw do
|
23
|
+
mount MailSpy::Engine => "/mailspy"
|
24
|
+
end
|
25
|
+
|
26
|
+
== Usage
|
27
|
+
|
28
|
+
To start using the viral tool simply enable it by
|
29
|
+
|
30
|
+
Adding the Virality module to Application Controller
|
31
|
+
class ApplicationController < ActionController::Base
|
32
|
+
include Virality
|
33
|
+
before_filter :track_virality
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
This will give you access to the following methods in lib/virality.rb these
|
38
|
+
methods are the complete client interface for the tool.
|
39
|
+
|
40
|
+
You will want to get started by instrumenting you code with the record_* methods
|
41
|
+
and using the message_for_conceit to populate your channel messages
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
== packaging into a application
|
46
|
+
|
47
|
+
If you don't have a private gem server you can package the gem into your
|
48
|
+
application since its not open source...
|
49
|
+
|
50
|
+
1) gem build viral.gemspec
|
51
|
+
2) rvm 1.9.2@global gem install viral-0.7.3.gem
|
52
|
+
3) gem unpack viral -v '0.7.3' --target vendor/gems/
|
53
|
+
4) Add the gemspec file to the vendored unpacked gem.
|
54
|
+
5) bundle install
|
55
|
+
|
56
|
+
== Testing
|
57
|
+
|
58
|
+
You will need to add your own database.yml to the dummy app, rake db:create, and
|
59
|
+
rake db:migrate and rake db:test:prepare to get the dummy app setup.
|
60
|
+
|
61
|
+
Then you can simply run rake from the plugins test directory
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
== License
|
66
|
+
This project is property of Ooga Labs and not open source software
|
67
|
+
|
68
|
+
This project rocks and uses MIT-LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'MailSpy'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
24
|
+
load 'rails/tasks/engine.rake'
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
Bundler::GemHelper.install_tasks
|
29
|
+
|
30
|
+
require 'rake/testtask'
|
31
|
+
|
32
|
+
Rake::TestTask.new(:test) do |t|
|
33
|
+
t.libs << 'lib'
|
34
|
+
t.libs << 'test'
|
35
|
+
t.pattern = 'test/**/*_test.rb'
|
36
|
+
t.verbose = false
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
task :default => :test
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require_tree .
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module MailSpy
|
2
|
+
class TrackingController < ApplicationController
|
3
|
+
|
4
|
+
def link
|
5
|
+
email_id = params[:eid]
|
6
|
+
head :unprocessable_entity and return if email_id.blank?
|
7
|
+
|
8
|
+
email = MailSpy::Email.find(email_id)
|
9
|
+
email.actions.create!(
|
10
|
+
:action_type => MailSpy::Action::ACTION_TYPE_CLICK,
|
11
|
+
:details => {
|
12
|
+
:url => params[:url],
|
13
|
+
:link_number => params[:n]
|
14
|
+
}
|
15
|
+
)
|
16
|
+
|
17
|
+
redirect_to params[:url]
|
18
|
+
end
|
19
|
+
|
20
|
+
def bug
|
21
|
+
email_id = params[:eid]
|
22
|
+
head :unprocessable_entity and return if email_id.blank?
|
23
|
+
|
24
|
+
#Mark the email as opened
|
25
|
+
email = MailSpy::Email.find(email_id)
|
26
|
+
email.actions.create!(
|
27
|
+
:action_type => MailSpy::Action::ACTION_TYPE_OPEN
|
28
|
+
)
|
29
|
+
|
30
|
+
head 200
|
31
|
+
end
|
32
|
+
|
33
|
+
def action
|
34
|
+
email_id = params[:eid]
|
35
|
+
action_type = params[:action_type]
|
36
|
+
details = params[:details]
|
37
|
+
|
38
|
+
head :unprocessable_entity and return if email_id.blank? || action_type.blank?
|
39
|
+
head :unprocessable_entity and return if details.present? && !details.kind_of?(Hash)
|
40
|
+
|
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
|
+
|
50
|
+
head 200
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module MailSpy
|
2
|
+
module EmailHelper
|
3
|
+
|
4
|
+
# Support for link clicks.
|
5
|
+
def track_link(*args, &block)
|
6
|
+
@_track_count ||= 0
|
7
|
+
if block_given?
|
8
|
+
options = args.first || {}
|
9
|
+
html_options = args.second
|
10
|
+
track_link(capture(&block), options, html_options)
|
11
|
+
else
|
12
|
+
@_track_count += 1
|
13
|
+
name = args[0]
|
14
|
+
options = args[1] || {}
|
15
|
+
html_options = args[2]
|
16
|
+
|
17
|
+
html_options = convert_options_to_data_attributes(options, html_options)
|
18
|
+
url = url_for(options)
|
19
|
+
|
20
|
+
href = html_options['href']
|
21
|
+
tag_options = tag_options(html_options)
|
22
|
+
|
23
|
+
# 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
|
+
|
27
|
+
|
28
|
+
href_attr = "href=\"#{ERB::Util.html_escape(url)}\"" unless href
|
29
|
+
"<a #{href_attr}#{tag_options}>#{ERB::Util.html_escape(name || url)}</a>".html_safe
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# Support for open tracking, client support, etc
|
35
|
+
def tracking_bug
|
36
|
+
"<img src='#{url_for(:controller => "mail_spy/tracking", :action => :bug, :eid => @_email_id)}' />".html_safe
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module MailSpy
|
2
|
+
class CoreMailer < ActionMailer::Base
|
3
|
+
helper "mail_spy/email"
|
4
|
+
|
5
|
+
def template(email)
|
6
|
+
# Slight hack to provide information to helpers
|
7
|
+
@_email_id = email.id
|
8
|
+
@template_values = email.template_values
|
9
|
+
|
10
|
+
# Set Headers
|
11
|
+
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
|
+
|
16
|
+
# Content of the email
|
17
|
+
html_erb = email.email_template.html_erb || ""
|
18
|
+
text_erb = email.email_template.html_erb || ""
|
19
|
+
|
20
|
+
mail_message = mail(email_hash) do |format|
|
21
|
+
format.text { render :inline => text_erb }
|
22
|
+
format.html { render :inline => html_erb }
|
23
|
+
end
|
24
|
+
|
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
|
+
mail_message.delivery_method.settings.merge!(
|
33
|
+
{
|
34
|
+
:address => esp.address,
|
35
|
+
:user_name => esp.user_name,
|
36
|
+
:password => esp.password,
|
37
|
+
:port => esp.port,
|
38
|
+
:authentication => esp.authentication,
|
39
|
+
:enable_starttls_auto => esp.enable_starttls_auto,
|
40
|
+
:domain => esp.domain
|
41
|
+
})
|
42
|
+
|
43
|
+
mail_message
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def set_if_present(email_hash, email, hash_key, email_key=nil)
|
50
|
+
email_key = hash_key if email_key.nil?
|
51
|
+
value = email.send("#{email_key}")
|
52
|
+
email_hash[hash_key] = value if value.present?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MailSpy
|
2
|
+
class Action
|
3
|
+
include Mongoid::Document
|
4
|
+
include Mongoid::Timestamps
|
5
|
+
|
6
|
+
ACTION_TYPE_OPEN = 'open'
|
7
|
+
ACTION_TYPE_CLICK = 'click'
|
8
|
+
ACTION_TYPE_BOUNCE = 'bounce'
|
9
|
+
ACTION_TYPE_UNSUBSCRIBE = 'unsubscribe'
|
10
|
+
ACTION_TYPE_SPAM = 'spam'
|
11
|
+
|
12
|
+
field :action_type, :type => String
|
13
|
+
field :count, :type => Integer, :default => 1
|
14
|
+
field :details, :type => Hash
|
15
|
+
embedded_in :email, :class_name => "MailSpy::Email"
|
16
|
+
|
17
|
+
|
18
|
+
def details
|
19
|
+
self.read_attribute(:details).try(:with_indifferent_access)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module MailSpy
|
2
|
+
class Email
|
3
|
+
include Mongoid::Document
|
4
|
+
include Mongoid::Timestamps
|
5
|
+
|
6
|
+
# Standard Email options
|
7
|
+
field :to
|
8
|
+
field :cc
|
9
|
+
field :bcc
|
10
|
+
field :from
|
11
|
+
field :subject
|
12
|
+
field :sender #Sets envelope from (aka sender header)
|
13
|
+
field :reply_to
|
14
|
+
field :headers
|
15
|
+
field :message_id #Sets the unique id for each email
|
16
|
+
|
17
|
+
# Email content
|
18
|
+
field :template_name, :type => String
|
19
|
+
field :template_values, :type => Hash
|
20
|
+
belongs_to :email_template, :class_name => 'MailSpy::EmailTemplate'
|
21
|
+
|
22
|
+
# Support for tracking which esp ran the email
|
23
|
+
field :email_service_provider, :type => String
|
24
|
+
|
25
|
+
# References back to a user in another db
|
26
|
+
field :user_id, :type => Integer
|
27
|
+
|
28
|
+
# Structured campaign, stream and component settings
|
29
|
+
field :campaign, :type => String
|
30
|
+
field :stream, :type => String
|
31
|
+
field :component, :type => String
|
32
|
+
|
33
|
+
# Scheduling and completion
|
34
|
+
field :schedule_at, :type => DateTime
|
35
|
+
field :sent, :type => Boolean
|
36
|
+
index :schedule_at
|
37
|
+
|
38
|
+
# Record keeping of what happened
|
39
|
+
embeds_many :actions, :class_name => "MailSpy::Action"
|
40
|
+
|
41
|
+
|
42
|
+
def template_values
|
43
|
+
self.read_attribute(:template_values).try(:with_indifferent_access)
|
44
|
+
end
|
45
|
+
|
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
|
+
# Great for debugging emails
|
59
|
+
def parsed_html_content
|
60
|
+
return MailSpy::CoreMailer.template(self).text_part.body.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
def parsed_text_content
|
64
|
+
return MailSpy::CoreMailer.template(self).html_part.body.to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def self.generate_campaign_stats
|
69
|
+
|
70
|
+
map = <<-eof
|
71
|
+
function(){
|
72
|
+
emit(this.campaign, {count: 1});
|
73
|
+
}
|
74
|
+
eof
|
75
|
+
|
76
|
+
reduce = <<-eof
|
77
|
+
function(key, values){
|
78
|
+
var result = { count : 0 };
|
79
|
+
|
80
|
+
values.forEach(function(value){
|
81
|
+
result.count += value.count;
|
82
|
+
});
|
83
|
+
return result;
|
84
|
+
}
|
85
|
+
eof
|
86
|
+
|
87
|
+
|
88
|
+
results = collection.map_reduce(map, reduce, :out => "campaign_reports")
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module MailSpy
|
2
|
+
class EmailTemplate
|
3
|
+
include Mongoid::Document
|
4
|
+
include Mongoid::Timestamps
|
5
|
+
field :name, :type => String
|
6
|
+
field :html_erb, :type => String
|
7
|
+
field :text_erb, :type => String
|
8
|
+
field :template_values_definition, :type => Hash
|
9
|
+
|
10
|
+
index :name, :unique => true
|
11
|
+
|
12
|
+
has_many :emails, :class_name => 'MailSpy::Email'
|
13
|
+
|
14
|
+
validates_presence_of :name
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>MailSpy</title>
|
5
|
+
<%= stylesheet_link_tag "mail_spy/application", :media => "all" %>
|
6
|
+
<%= javascript_include_tag "mail_spy/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module MailSpy
|
2
|
+
class InitializerGenerator < Rails::Generators::Base
|
3
|
+
source_root File.expand_path("../templates", __FILE__)
|
4
|
+
|
5
|
+
desc "Generator creates a sample initializer"
|
6
|
+
|
7
|
+
def create_config_files
|
8
|
+
copy_file "mail_spy.rb", "config/initializers/mail_spy.rb"
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# MailSpy allows you to add multiple email service providers simply
|
2
|
+
# call this method and specify the following options
|
3
|
+
#
|
4
|
+
# :name (must be unique for all esps you add)
|
5
|
+
# :via (stmp or sendmail)
|
6
|
+
#
|
7
|
+
# SMTP OPTIONS
|
8
|
+
# :address,
|
9
|
+
# :port,
|
10
|
+
# :authentication,
|
11
|
+
# :user_name,
|
12
|
+
# :password,
|
13
|
+
# :domain,
|
14
|
+
# :enable_starttls_auto
|
15
|
+
|
16
|
+
MailSpy.add_email_service_provider do |esp|
|
17
|
+
esp.name = "sendgrid"
|
18
|
+
esp.address = "smtp.sendgrid.net"
|
19
|
+
esp.port = "587",
|
20
|
+
esp.domain = "herokuapp.com"
|
21
|
+
esp.user_name = ENV['SENDGRID_USERNAME']
|
22
|
+
esp.password = ENV['SENDGRID_PASSWORD']
|
23
|
+
esp.authentication = :plain
|
24
|
+
esp.enable_starttls_auto = true
|
25
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module MailSpy
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace MailSpy
|
4
|
+
|
5
|
+
# http://edgeapi.rubyonrails.org/classes/Rails/Engine.html
|
6
|
+
config.generators do |g|
|
7
|
+
g.orm :mongoid
|
8
|
+
g.template_engine :erb
|
9
|
+
g.test_framework :test_unit
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module MailSpy
|
2
|
+
module Manager
|
3
|
+
|
4
|
+
# ------------------------------------------- ADD_TEMPLATE
|
5
|
+
# Programmatically add email templates with a schema of search and
|
6
|
+
# replaceable text
|
7
|
+
#
|
8
|
+
def add_template(name, html_erb, text_erb, template_values_definition)
|
9
|
+
MailSpy::EmailTemplate.create!(
|
10
|
+
{
|
11
|
+
:name => name,
|
12
|
+
:html_erb => html_erb,
|
13
|
+
:text_erb => text_erb,
|
14
|
+
:template_values_definition => template_values_definition
|
15
|
+
})
|
16
|
+
end
|
17
|
+
|
18
|
+
# ------------------------------------------- ADD INSTANCE
|
19
|
+
# Adds a instance of a email template to the queue to send
|
20
|
+
#
|
21
|
+
def add_instance(options)
|
22
|
+
options.to_options!
|
23
|
+
|
24
|
+
required_options = [
|
25
|
+
:template_name, :campaign, :stream, :component, :schedule_at, :subject,
|
26
|
+
:template_values, :from, :reply_to]
|
27
|
+
to_options = [:to, :cc, :bcc]
|
28
|
+
|
29
|
+
# Ensure that we have the required options
|
30
|
+
required_options.each { |ro| raise "add_instance call missing #{ro}" unless options.include? ro }
|
31
|
+
|
32
|
+
# Make sure we have someone to send to
|
33
|
+
has_sender = options.keys.select { |option| to_options.include? option.intern }.present?
|
34
|
+
raise "Email instance has no sender (to,cc,bcc were all blank)" unless has_sender
|
35
|
+
|
36
|
+
# Make sure we have a template
|
37
|
+
options[:template_name]
|
38
|
+
template = MailSpy::EmailTemplate.first(conditions: {name: options[:template_name]})
|
39
|
+
raise "No template: #{options[:template_name]} found, try add_template first" unless template
|
40
|
+
|
41
|
+
|
42
|
+
# Make sure that
|
43
|
+
(required_options + to_options).each do |option|
|
44
|
+
unless MailSpy::Email.method_defined? "#{option}=".intern
|
45
|
+
raise "MailSpy::Email doesn't have #{option} as a setter '"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
email = MailSpy::Email.create!(options)
|
51
|
+
email.email_template = template
|
52
|
+
email.save!
|
53
|
+
email
|
54
|
+
end
|
55
|
+
|
56
|
+
# ------------------------------------------- SEND OUTSTANDING EMAILS
|
57
|
+
# Batches through all the emails that were scheduled and have come due
|
58
|
+
# sends them out (step many at a time)
|
59
|
+
def send_outstanding_emails(step=100)
|
60
|
+
raise "No Email service providers installed" unless MailSpy.esps.present?
|
61
|
+
|
62
|
+
offset = 0
|
63
|
+
sent = 0
|
64
|
+
|
65
|
+
# Helper function for setting present values
|
66
|
+
def set_if_present(email, pony_hash, pony_key, email_key=nil)
|
67
|
+
email_key = pony_key if email_key.nil?
|
68
|
+
value = email.send("#{email_key}")
|
69
|
+
pony_hash[pony_key] = value if value.present?
|
70
|
+
end
|
71
|
+
|
72
|
+
while (true)
|
73
|
+
mails = MailSpy::Email.
|
74
|
+
limit(step).offset(offset).
|
75
|
+
where(:schedule_at.lte => DateTime.now, :sent => false).all
|
76
|
+
break if mails.blank?
|
77
|
+
mails.each do |email|
|
78
|
+
MailSpy::CoreMailer.template(email).deliver
|
79
|
+
email.update_attribute(:sent, true)
|
80
|
+
sent += 1
|
81
|
+
end
|
82
|
+
offset += step
|
83
|
+
end
|
84
|
+
|
85
|
+
sent
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# ------------------------------------------- TRACKING
|
90
|
+
#
|
91
|
+
|
92
|
+
def track_other_action
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|