module MailSpy class Email include Mongoid::Document include Mongoid::Timestamps @@template_cache = {} # Standard Email options field :to field :cc field :bcc field :from field :subject field :sender #Sets envelope from (aka sender header) field :reply_to field :headers, :type => Hash, :default => {} field :message_id #Sets the unique id for each email # Email content field :template_values, :type => Hash, :default => {} # Support for tracking which esp ran the email field :email_service_provider, :type => String validates_presence_of :email_service_provider # References back to a user in another db field :user_id, :type => Integer # Structured campaign, stream and component settings field :campaign, :type => String field :stream, :type => String field :component, :type => String # Scheduling and completion field :schedule_at, :type => DateTime field :sent, :type => Boolean, :default => false index :schedule_at index :sent # Error reporting field :failed, :type => Boolean, :default => false field :error_message, :type => String field :error_backtrace, :type => Array index :failed # Record keeping of what happened embeds_many :actions, :class_name => "MailSpy::Action" # Google Analytics tracking field :utm_source, :type => String field :utm_medium, :type => String field :utm_campaign, :type => String field :utm_term, :type => String field :utm_content, :type => String def template_values self.read_attribute(:template_values).try(:with_indifferent_access) end # Great for debugging emails def parsed_html_content return MailSpy::CoreMailer.template(self).html_part.body.to_s end def parsed_text_content return MailSpy::CoreMailer.template(self).text_part.body.to_s end def html_erb load_template("html.erb") end def text_erb load_template("text.erb") end def deliver begin MailSpy::CoreMailer.template(self).deliver self.update_attribute(:sent, true) rescue Exception => e self.failed = true self.error_message = e.try(:message) self.error_backtrace = e.try(:backtrace) self.save! end end protected # Loads a template file from s3 using def load_template(suffix) raise "No aws_campaign_bucket_set" if MailSpy.aws_campaign_bucket.strip.blank? # Check the cache using our convention path = "#{self.campaign}/#{self.stream}/#{self.component}.#{suffix}" return @@template_cache[path] if @@template_cache[path].present? # Load the object from s3 s3 = AWS::S3.new(:access_key_id => MailSpy.aws_access_key_id, :secret_access_key => MailSpy.aws_secret_access_key) campaign_bucket = s3.buckets[MailSpy.aws_campaign_bucket] campaign_bucket = buckets.create(MailSpy.aws_campaign_bucket) unless campaign_bucket.exists? object = campaign_bucket.objects[path] raise "no object found at path: #{path}" unless object.exists? # Read and return @@template_cache[path] = (object.read || "") return @@template_cache[path] end def self.generate_subject_reports map = <<-eof function(){ var action_counts = {}; for(var i=0, len = this.actions.length; i < len; i++){ var action = this.actions[i]; action_counts[action.action_type] = action_counts[action.action_type] || 0; action_counts[action.action_type]++; } emit({ campaign:this.campaign, stream:this.stream, component:this.component, subject:this.subject }, action_counts); } eof reduce = <<-eof function(key, values){ var result = {}; values.forEach(function(hash){ for(var key in hash){ result[key] = result[key] || 0 result[key] += hash[key] } }); return result; } eof results = collection.map_reduce(map, reduce, :out => "test_reports") end def self.generate_campaign_stats map = <<-eof function(){ emit(this.campaign, {count: 1}); } eof reduce = <<-eof function(key, values){ var result = { count : 0 }; values.forEach(function(value){ result.count += value.count; }); return result; } eof results = collection.map_reduce(map, reduce, :out => "campaign_reports") end end end