module MailSpy module Manager # ------------------------------------------- CREATE EMAIL # Adds a instance of a email template to the queue to send # def create_email(options) options.to_options! required_options = [ :campaign, :stream, :component, :schedule_at, :subject, :template_values, :from, :reply_to] to_options = [:to, :cc, :bcc] # Ensure that we have the required options required_options.each { |ro| raise "create_email call missing #{ro}" unless options.include? ro } # Ensure that the campaign, stream and component are not blank # These keys are used to define the s3 paths for the templates [:campaign, :stream, :component].each { |key| raise "##{key} can't be blank" if options[key].blank? } # Make sure we have someone to send to and its not blank has_sender = to_options.select { |option| options[option].present? }.present? raise "Email instance has no sender (to,cc,bcc were all blank)" unless has_sender # Make sure that all options passed map to a accessor so we don't errantly # think we are passing something correctly and really its getting silently # ignored options.keys.each do |option| unless MailSpy::Email.method_defined? "#{option}=".intern raise "MailSpy::Email doesn't have #{option} as a setter '" end end # Ensure that a esp (forced or random) exists for the email forced_esp = options[:email_service_provider] if forced_esp.present? raise "No esp configured with name: #{forced_esp}" if MailSpy.esps[forced_esp].blank? else raise "No esps configured" if MailSpy.esps.blank? esp_key = MailSpy.esps.keys[rand(MailSpy.esps.keys.count)] options[:email_service_provider] = esp_key end # Google Analytics aupport for automatic population of utm_tokens esp = MailSpy.esps[options[:email_service_provider]] if esp.options[:enable_auto_google_analytics].present? options[:utm_source] ||= 'mailspy' options[:utm_medium] ||= 'email' options[:utm_campaign] ||= options[:campaign] options[:utm_term] ||= options[:stream] options[:utm_content] ||= options[:component] end #Create the email email = MailSpy::Email.new(options) # Ensure we have the template for the email on s3 raise "Missing html template" unless email.html_erb.present? raise "Missing text template" unless email.text_erb.present? email.save! # Enable sendgrid specific enhancements # Must happen after the email has been saved for the id if esp.options[:enable_sendgrid_event_tracking].present? header = MailSpy::Sendgrid::SmtpApiHeader.new header.setUniqueArgs({:eid => email.id.to_s}) email.headers = {'X-SMTPAPI' => header.asJSON}.merge(email.headers) email.save! end email end # ------------------------------------------- SEND OUTSTANDING EMAILS # Batches through all the emails that were scheduled and have come due # sends them out (step many at a time). Don't thread this method, instead # use the parameters to control concurrency def send_outstanding_emails(step=100, num_threads=50) success = false raise "No Email service providers installed" unless MailSpy.esps.present? return if MailSpy::ProcessLog.currently_processing? current_process = MailSpy::ProcessLog.create!( { :start => Time.now, :running => true, }) wq = WorkQueue.new(num_threads, step*2) current_time = DateTime.now offset = 0 sent = 0 # Helper function for setting present values def set_if_present(email, pony_hash, pony_key, email_key=nil) email_key = pony_key if email_key.nil? value = email.send("#{email_key}") pony_hash[pony_key] = value if value.present? end while true emails = MailSpy::Email. limit(step).offset(offset).asc(:_id). where(:schedule_at.lte => current_time, :sent => false, :failed => false). collect break if emails.count <= 0 #returns enumerator which is never blank emails.each do |email| wq.enqueue_b do begin MailSpy::CoreMailer.template(email).deliver email.update_attribute(:sent, true) sent += 1 rescue Exception => e email.failed = true email.error_message = e.try(:message) email.error_backtrace = e.try(:backtrace) email.save! end end end # We must join here otherwise the next loop email lookup will be in a # race condition with the results of our worker_queue. wq.join offset += step end success = true return sent ensure if current_process end_time = Time.now current_process.end = end_time current_process.seconds_elapsed = end_time.to_i - current_process.start.to_i current_process.running = false current_process.success = success current_process.save! end end # ------------------------------------------- TRACKING # MailSpy will automatically track opens and clicks if the tracking bugs # and track_link helpers are used. This action allows tracking of # arbitrary actions. Please be careful to standardize on action names # email_id: The id from the MailSpy::Email record # action_type: String denoting the action that occured # details: Hash of any details of that action (again strive for standards) # count: how many of this action occurred (defaults to 1) def track_action(email_id, action_type, details={}, count=1) raise "track_action missing email_id" if email_id.blank? raise "track_Action missing action_type" if action_type.blank? hash ={} hash[:action_type] = action_type hash[:count] = count hash[:details] = details if details.present? && details.kind_of?(Hash) # Save it up email = MailSpy::Email.find(email_id) email.actions.create!(hash) end end end