Class: CampaignEmail

Inherits:
CampaignAction show all
Includes:
Memery, Models::LiquidMethods
Defined in:
app/models/campaign_email.rb

Overview

== Schema Information

Table name: campaign_actions
Database name: primary

id :integer not null, primary key
description :text
frequency :integer
last_transmitted :datetime
name :string
scheduled_time :datetime
sender_email :string
sequence :integer
state :string
type :string
created_at :datetime not null
updated_at :datetime not null
campaign_id :integer
creator_id :integer
email_template_id :integer
sender_id :integer
source_id :integer
updater_id :integer

Indexes

campaign_actions_campaign_id_idx (campaign_id)
campaign_actions_email_template_id_idx (email_template_id)
idx_type (type)

Foreign Keys

fk_rails_... (campaign_id => campaigns.id)
fk_rails_... (email_template_id => email_templates.id)

Constant Summary collapse

FREQUENCIES =
{ 'daily' => 86_400, 'weekly' => 604_800 }.freeze
SPECIAL_SENDERS =
{ 'Social' => 'social@warmlyyours.com' }.freeze
STATES =

:percentage_for_processed,
:percentage_for_deferred,
:percentage_for_delivered,
:percentage_for_open,
:percentage_for_click,
:percentage_for_bounce,
:percentage_for_dropped,
:percentage_for_spamreport,
:percentage_for_unsubscribe,

%w[
  pending
  queued
  exception
  suppressed
  duplicate
  deferred
  dropped
  bounced
  sent
  processed
  delivered
  opened
  clicked
  spammed
  unsubscribed
].freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#clone_email_template_idObject

Returns the value of attribute clone_email_template_id.



41
42
43
# File 'app/models/campaign_email.rb', line 41

def clone_email_template_id
  @clone_email_template_id
end

#nameObject (readonly)

before_destroy :can_be_destroyed?

Validations:



85
# File 'app/models/campaign_email.rb', line 85

validates :email_template, :name, presence: true

#sender_emailObject (readonly)



86
# File 'app/models/campaign_email.rb', line 86

validates :sender_email, presence: true

Class Method Details

.combined_states_for_selectObject



166
167
168
# File 'app/models/campaign_email.rb', line 166

def self.combined_states_for_select
  STATES.map { |state| [state.to_s.titleize, state] }
end

.frequencies_selectObject



162
163
164
# File 'app/models/campaign_email.rb', line 162

def self.frequencies_select
  FREQUENCIES.collect { |text, seconds| [text, seconds] }
end

.ready_to_be_transmittedActiveRecord::Relation<CampaignEmail>

A relation of CampaignEmails that are ready to be transmitted. Active Record Scope

Returns:

See Also:



92
# File 'app/models/campaign_email.rb', line 92

scope :ready_to_be_transmitted, -> { joins(:campaign).where(campaigns: { state: 'active' }, state: 'scheduled').where(CampaignEmail[:scheduled_time].lteq(Time.current)) }

.send_monthly_summary_email(date_start: nil, date_end: nil) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
# File 'app/models/campaign_email.rb', line 150

def self.send_monthly_summary_email(date_start: nil, date_end: nil)
  date_start ||= Date.current.beginning_of_month
  date_end ||= date_start.end_of_month
  date_range = (date_start..date_end)

  campaign_emails = CampaignEmail.where(last_transmitted: date_range).joins(:campaign).where('last_transmitted between ? and ? and exclude_from_monthly_report = false', Date.current.last_month.beginning_of_month.beginning_of_day,
Date.current.last_month.end_of_month.end_of_day).order('last_transmitted asc').to_a
  return 'No emails sent last month' unless campaign_emails.present?

  InternalMailer.campaign_summary(campaign_emails).deliver
end

.sender_optionsObject



146
147
148
# File 'app/models/campaign_email.rb', line 146

def self.sender_options
  (Employee.includes(:employee_account).active_employees.map(&:email_with_name) + CampaignEmail::SPECIAL_SENDERS.map { |name, email| "#{name} <#{email}>" }).sort_by { |s| s[0] }
end

Instance Method Details

#campaignCampaign

Returns:

See Also:



74
# File 'app/models/campaign_email.rb', line 74

belongs_to :campaign, inverse_of: :campaign_emails, optional: true

#campaign_deliveriesActiveRecord::Relation<CampaignDelivery>

Returns:

See Also:



77
# File 'app/models/campaign_email.rb', line 77

has_many :campaign_deliveries

#can_be_destroyed?Boolean

Returns:

  • (Boolean)


190
191
192
193
194
195
196
197
# File 'app/models/campaign_email.rb', line 190

def can_be_destroyed?
  if (unscheduled? || scheduled?) && campaign_deliveries.empty?
    true
  else
    errors.add :base, 'cannot delete campaign email which has already been sent'
    false
  end
end

#can_be_sent?Boolean

Returns:

  • (Boolean)


244
245
246
# File 'app/models/campaign_email.rb', line 244

def can_be_sent?
  unscheduled? or scheduled?
end

#can_be_transmitted?Boolean

Returns:

  • (Boolean)


240
241
242
# File 'app/models/campaign_email.rb', line 240

def can_be_transmitted?
  scheduled_time.present? and scheduled_time <= Time.current
end

#communication_recipientsActiveRecord::Relation<CommunicationRecipient>

Returns:

See Also:



78
# File 'app/models/campaign_email.rb', line 78

has_many :communication_recipients, through: :campaign_deliveries

#costObject



199
200
201
# File 'app/models/campaign_email.rb', line 199

def cost
  communication_recipients.count * 0.0008 # this is the cost per email on sendgrid
end

#delivery_funnel_countsObject

Returns a monotonic funnel of counts for easier human interpretation.
Keys are: :total_recipients, :suppressed, :processed, :bounced, :delivered, :opened, :clicked, :unsubscribed, :spammed



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'app/models/campaign_email.rb', line 276

def delivery_funnel_counts
  suppressed = campaign_deliveries.where(state: 'suppressed').count

  # Recipients that entered the transmission pipeline (a communication exists)
  processed_total = communication_recipients.count

  # Show the size of the original audience as Suppressed + Processed
  total_recipients = suppressed + processed_total

  # Outcome states from webhook processing
  bounced_total = communication_recipients.where(state: 'bounced').count
  delivered_total = communication_recipients.where(state: %w[delivered opened clicked spammed unsubscribed]).count
  opened_total = communication_recipients.where(state: %w[opened clicked]).count
  clicked_total = communication_recipients.where(state: 'clicked').count
  unsubscribed_total = communication_recipients.where(state: 'unsubscribed').count
  spammed_total = communication_recipients.where(state: 'spammed').count

  {
    total_recipients: total_recipients,
    suppressed: suppressed,
    processed: processed_total,
    bounced: bounced_total,
    delivered: delivered_total,
    opened: opened_total,
    clicked: clicked_total,
    unsubscribed: unsubscribed_total,
    spammed: spammed_total
  }
end

#delivery_percentagesObject



259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'app/models/campaign_email.rb', line 259

def delivery_percentages
  hsh = {}
  total = total_deliveries_count
  delivery_stats.each do |state, counter|
    percentage = if counter > 0 && total > 0
                   ((counter.to_f / total) * 100).round(2)
                 else
                   0.0
                 end
    hsh[state] = percentage
  end
  hsh
end

#delivery_statsObject



248
249
250
251
# File 'app/models/campaign_email.rb', line 248

def delivery_stats
  hsh = view_campaign_deliveries.group(:combined_state).count
  hsh.slice(*STATES) # Re-sorts by specific state order
end

#email_templateEmailTemplate

Validations:



75
# File 'app/models/campaign_email.rb', line 75

belongs_to :email_template, optional: true

#frequency_descriptionObject



209
210
211
# File 'app/models/campaign_email.rb', line 209

def frequency_description
  frequency.nil? ? 'One time' : FREQUENCIES.detect { |_k, v| v == frequency }[0]
end

#generate_email_dynamic_subscribersObject



217
218
219
# File 'app/models/campaign_email.rb', line 217

def generate_email_dynamic_subscribers
  campaign.subscriber_lists.where(list_type: 'email_dynamic').find_each(&:generate_subscribers)
end

#generate_sourceObject



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'app/models/campaign_email.rb', line 317

def generate_source
  return if campaign&.source_id.nil?

  s = Source.new
  s.parent_id = campaign.source_id
  s.name = name
  s.referral_code = s.generate_ref_code
  if s.save
    self.source_id = s.id
    true
  else
    errors.add(:base, "Unable to create source. Error: #{s.errors.full_messages}")
    false
  end
end

#last_transmitted_formattedObject



175
176
177
# File 'app/models/campaign_email.rb', line 175

def last_transmitted_formatted
  last_transmitted.to_fs(:compact)
end

#prepare_campaign_deliveriesObject



221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'app/models/campaign_email.rb', line 221

def prepare_campaign_deliveries
  generate_email_dynamic_subscribers
  records = campaign.subscribers.active.pluck(:id).map do |subscriber_id|
    {
      campaign_email_id: id,
      subscriber_id: subscriber_id,
      state: 'pending'
    }
  end
  require 'activerecord-import/base'
  require 'activerecord-import/active_record/adapters/postgresql_adapter'
  CampaignDelivery.import records, validate: true, on_duplicate_key_ignore: true
end

#recipient_countObject



179
180
181
# File 'app/models/campaign_email.rb', line 179

def recipient_count
  communication_recipients.count
end

#roiObject

return on investment



203
204
205
206
207
# File 'app/models/campaign_email.rb', line 203

def roi # return on investment
  return profit if cost.zero?

  ((profit - cost) / cost) * 100
end

#scheduled_time_descriptionObject



213
214
215
# File 'app/models/campaign_email.rb', line 213

def scheduled_time_description
  frequency.nil? ? 'Scheduled Time' : 'Next Scheduled Time'
end

#send_emailsObject



235
236
237
238
# File 'app/models/campaign_email.rb', line 235

def send_emails
  prepare_campaign_deliveries unless campaign_deliveries.present?
  CampaignDeliveryWorker.perform_async
end

#senderObject



170
171
172
173
# File 'app/models/campaign_email.rb', line 170

def sender
  email = Mail::Address.new(sender_email).address
  email.nil? ? nil : Employee.joins(:employee_account).where(accounts: { email: email }).first
end

#set_new_scheduled_timeObject



183
184
185
186
187
188
# File 'app/models/campaign_email.rb', line 183

def set_new_scheduled_time
  return unless frequency.present?

  update(scheduled_time: last_transmitted + frequency)
  reschedule
end

#sourceSource

Returns:

See Also:



76
# File 'app/models/campaign_email.rb', line 76

belongs_to :source, optional: true

#total_deliveries_countObject



254
255
256
# File 'app/models/campaign_email.rb', line 254

def total_deliveries_count
  delivery_stats.values.sum
end

#update_email_templateObject



313
314
315
# File 'app/models/campaign_email.rb', line 313

def update_email_template
  email_template.description = "Template for campaign email #{name}"
end

#view_campaign_deliveriesActiveRecord::Relation<ViewCampaignDelivery>

Returns:

See Also:



79
# File 'app/models/campaign_email.rb', line 79

has_many :view_campaign_deliveries