Class: CampaignEmail
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
Models::Auditable::ALWAYS_IGNORED
Constants included
from Schedulable
Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
#creator, #updater
Class Method Summary
collapse
Instance Method Summary
collapse
#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
config
#after_commit
#publish_event
Instance Attribute Details
#clone_email_template_id ⇒ Object
Returns the value of attribute clone_email_template_id.
42
43
44
|
# File 'app/models/campaign_email.rb', line 42
def clone_email_template_id
@clone_email_template_id
end
|
#name ⇒ Object
before_destroy :can_be_destroyed?
Validations:
88
|
# File 'app/models/campaign_email.rb', line 88
validates :email_template, :name, presence: true
|
#sender_email ⇒ Object
89
|
# File 'app/models/campaign_email.rb', line 89
validates :sender_email, presence: true
|
Class Method Details
.combined_states_for_select ⇒ Object
205
206
207
|
# File 'app/models/campaign_email.rb', line 205
def self.combined_states_for_select
STATES.map { |state| [state.to_s.titleize, state] }
end
|
.frequencies_select ⇒ Object
201
202
203
|
# File 'app/models/campaign_email.rb', line 201
def self.frequencies_select
FREQUENCIES.map { |text, seconds| [text, seconds] }
end
|
.ready_to_be_transmitted ⇒ ActiveRecord::Relation<CampaignEmail>
A relation of CampaignEmails that are ready to be transmitted. Active Record Scope
95
|
# File 'app/models/campaign_email.rb', line 95
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
189
190
191
192
193
194
195
196
197
198
199
|
# File 'app/models/campaign_email.rb', line 189
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' if campaign_emails.blank?
InternalReportsMailer.campaign_summary(campaign_emails).deliver
end
|
.sender_options ⇒ Object
185
186
187
|
# File 'app/models/campaign_email.rb', line 185
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
77
|
# File 'app/models/campaign_email.rb', line 77
belongs_to :campaign, inverse_of: :campaign_emails, optional: true
|
#campaign_deliveries ⇒ ActiveRecord::Relation<CampaignDelivery>
80
|
# File 'app/models/campaign_email.rb', line 80
has_many :campaign_deliveries
|
#can_be_destroyed? ⇒ Boolean
239
240
241
242
243
244
245
246
|
# File 'app/models/campaign_email.rb', line 239
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
293
294
295
|
# File 'app/models/campaign_email.rb', line 293
def can_be_sent?
unscheduled? or scheduled?
end
|
#can_be_transmitted? ⇒ Boolean
289
290
291
|
# File 'app/models/campaign_email.rb', line 289
def can_be_transmitted?
scheduled_time.present? and scheduled_time <= Time.current
end
|
#communication_recipients ⇒ ActiveRecord::Relation<CommunicationRecipient>
81
|
# File 'app/models/campaign_email.rb', line 81
has_many :communication_recipients, through: :campaign_deliveries
|
#cost ⇒ Object
248
249
250
|
# File 'app/models/campaign_email.rb', line 248
def cost
communication_recipients.count * 0.0008 end
|
#delivery_funnel_counts ⇒ Object
Returns a monotonic funnel of counts for easier human interpretation.
Keys are: :total_recipients, :suppressed, :processed, :bounced, :delivered, :opened, :clicked, :unsubscribed, :spammed
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
|
# File 'app/models/campaign_email.rb', line 325
def delivery_funnel_counts
suppressed = campaign_deliveries.where(state: 'suppressed').count
processed_total = communication_recipients.count
total_recipients = suppressed + processed_total
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
machine_opened_total = communication_recipients.where(state: %w[opened clicked], machine_open: true).count
human_opened_total = opened_total - machine_opened_total
clicked_total = communication_recipients.where(state: 'clicked').count
machine_clicked_total = communication_recipients.where(state: 'clicked', machine_clicked: true).count
human_clicked_total = clicked_total - machine_clicked_total
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,
machine_opened: machine_opened_total,
human_opened: human_opened_total,
clicked: clicked_total,
machine_clicked: machine_clicked_total,
human_clicked: human_clicked_total,
unsubscribed: unsubscribed_total,
spammed: spammed_total
}
end
|
#delivery_percentages ⇒ Object
308
309
310
311
312
313
314
315
316
317
318
319
320
|
# File 'app/models/campaign_email.rb', line 308
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_stats ⇒ Object
297
298
299
300
|
# File 'app/models/campaign_email.rb', line 297
def delivery_stats
hsh = view_campaign_deliveries.group(:combined_state).count
hsh.slice(*STATES) end
|
Validations:
78
|
# File 'app/models/campaign_email.rb', line 78
belongs_to :email_template, optional: true
|
#estimated_audience_size ⇒ Integer
Estimated audience size before transmission, mirroring the subscribers
used by #prepare_campaign_deliveries. Does not include subscribers
that would be added by generate_email_dynamic_subscribers at send time.
228
229
230
|
# File 'app/models/campaign_email.rb', line 228
def estimated_audience_size
campaign.subscribers.active.distinct.count
end
|
#frequency_description ⇒ Object
259
260
261
|
# File 'app/models/campaign_email.rb', line 259
def frequency_description
frequency.nil? ? 'One time' : FREQUENCIES.find { |_k, v| v == frequency }[0]
end
|
#generate_email_dynamic_subscribers ⇒ Object
267
268
269
|
# File 'app/models/campaign_email.rb', line 267
def generate_email_dynamic_subscribers
campaign.subscriber_lists.where(list_type: 'email_dynamic').find_each(&:generate_subscribers)
end
|
#generate_source ⇒ Object
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
|
# File 'app/models/campaign_email.rb', line 383
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
|
214
215
216
|
# File 'app/models/campaign_email.rb', line 214
def last_transmitted_formatted
last_transmitted.to_fs(:compact)
end
|
#prepare_campaign_deliveries ⇒ Object
271
272
273
274
275
276
277
278
279
280
281
282
|
# File 'app/models/campaign_email.rb', line 271
def prepare_campaign_deliveries
generate_email_dynamic_subscribers
records = campaign.subscribers.active.ids.map do |subscriber_id|
{
campaign_email_id: id,
subscriber_id: subscriber_id,
state: 'pending'
}
end
CampaignDelivery.insert_all(records, unique_by: %i[campaign_email_id subscriber_id]) if records.any?
end
|
#recipient_count ⇒ Object
218
219
220
|
# File 'app/models/campaign_email.rb', line 218
def recipient_count
communication_recipients.count
end
|
#roi ⇒ Object
253
254
255
256
257
|
# File 'app/models/campaign_email.rb', line 253
def roi
return profit if cost.zero?
((profit - cost) / cost) * 100
end
|
#scheduled_time_description ⇒ Object
263
264
265
|
# File 'app/models/campaign_email.rb', line 263
def scheduled_time_description
frequency.nil? ? 'Scheduled Time' : 'Next Scheduled Time'
end
|
#send_emails ⇒ Object
284
285
286
287
|
# File 'app/models/campaign_email.rb', line 284
def send_emails
prepare_campaign_deliveries if campaign_deliveries.blank?
CampaignDeliveryWorker.perform_async
end
|
#sender ⇒ Object
209
210
211
212
|
# File 'app/models/campaign_email.rb', line 209
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_time ⇒ Object
232
233
234
235
236
237
|
# File 'app/models/campaign_email.rb', line 232
def set_new_scheduled_time
return if frequency.blank?
update(scheduled_time: last_transmitted + frequency)
reschedule
end
|
79
|
# File 'app/models/campaign_email.rb', line 79
belongs_to :source, optional: true
|
#total_deliveries_count ⇒ Object
303
304
305
|
# File 'app/models/campaign_email.rb', line 303
def total_deliveries_count
delivery_stats.values.sum
end
|
#update_email_template ⇒ Object
377
378
379
380
381
|
# File 'app/models/campaign_email.rb', line 377
def update_email_template
return unless email_template&.new_record?
email_template.description = "Template for campaign email #{name}"
end
|
#view_campaign_deliveries ⇒ ActiveRecord::Relation<ViewCampaignDelivery>
82
|
# File 'app/models/campaign_email.rb', line 82
has_many :view_campaign_deliveries
|