mail_spy 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|