Subscription-Based Email Communication
This document outlines automated emails sent to users with active subscriptions in the Kulcare platform.
Overview
Only users with active subscriptions receive automated engagement emails. This ensures we only send value-added content to paying customers.
Active Subscription Criteria
An active subscription is defined as:
- Subscription status:
active,authenticated, orto_be_cancelled current_enddate is in the future (>= current time)- Timezone-aware validation using system default timezone
Monthly Emails
Monthly Review Summary
Frequency: Monthly
Recipients: Clinic owners/admins with active subscriptions
Rake Task: backend/lib/tasks/cron_monthly_monthly_review_summary.rake
Mailer: ClinicReviewMailer.monthly_review_summary
Purpose
Provides a comprehensive monthly summary of clinic performance:
- Patient reviews and ratings
- Review trends and insights
- Response rate to patient feedback
- Overall satisfaction metrics
Eligibility Criteria
- ✅ Clinic has an active subscription
- ✅ Clinic is linked to a user with billing customer record
Technical Implementation
# Query filters clinics with active subscriptions
current_time = Time.now.in_time_zone(Settings['default_timezone']).utc
active_status_ids = BasicBillingSubscriptionStatus.active_ids
# Skip if no active statuses configured
if active_status_ids.blank?
Rails.logger.warn('No active subscription statuses; skipping monthly review summary task')
next
end
Clinic.joins("INNER JOIN user_accounts ON user_accounts.profile_id = clinics.id
AND user_accounts.account_id = #{Account::CLINIC_ID}")
.joins('INNER JOIN users ON users.id = user_accounts.user_id')
.joins('INNER JOIN billing_customers ON billing_customers.user_id = users.id')
.joins('INNER JOIN billing_subscriptions ON billing_subscriptions.billing_customer_id = billing_customers.id')
.where('billing_subscriptions.current_end >= ?', current_time)
.where(billing_subscriptions: { basic_billing_subscription_status_id: active_status_ids })
.distinct
.find_in_batches(batch_size: 100) do |batch|
batch.each do |clinic|
ClinicReviewMailer.monthly_review_summary(clinic).deliver_now
Rails.logger.info "Clinic id: #{clinic.id}"
end
GC.start
endDelivery Method
- Sent via:
deliver_now(synchronous) - Batch size: 100 clinics per batch
- Memory management: GC after each batch
Cron Schedule
# Runs on the 1st day of every month at 9:00 AM
0 9 1 * * bundle exec rake db:monthly_review_summarySample Output
2025-01-16 10:00:00 +0530
Rake Task Started....
------------------------------------------------------
Active subscription status IDs: 1, 2, 3
Clinic id: 12
Clinic id: 13
Sent to 15 clinics with active subscriptions
------------------------------------------------------
Rake Task Ended...
======================================================Weekly Emails
Weekly Clinic Statistics
Frequency: Weekly
Recipients: Non-private clinic owners with active subscriptions
Rake Task: backend/lib/tasks/cron_weekly_clinic_stats_email.rake
Mailer: ClinicMailer.weekly_stats
Purpose
Provides weekly operational statistics and insights:
- Appointment metrics (total, completed, cancelled, no-shows)
- Patient visit trends
- Revenue summary
- Week-over-week comparisons
- Key performance indicators (KPIs)
Eligibility Criteria
- ✅ Clinic has an active subscription
- ✅ Clinic is NOT a private clinic (
private_clinic: false) - ✅ Feature flag
SEND_WEEKLY_CLINIC_EMAILis enabled for the clinic - ✅ Stats have been calculated and stored in
clinic_weekly_statstable
Two-Phase Process
Phase 1: Calculate Statistics
# Calculate stats for all eligible clinics
current_time = Time.now.in_time_zone(Settings['default_timezone']).utc
active_status_ids = BasicBillingSubscriptionStatus.active_ids
# Skip if no active statuses configured
if active_status_ids.blank?
Rails.logger.warn('No active subscription statuses; skipping weekly stats email task')
next
end
Clinic.where(private_clinic: false)
.joins("INNER JOIN user_accounts ON user_accounts.profile_id = clinics.id
AND user_accounts.account_id = #{Account::CLINIC_ID}")
.joins('INNER JOIN users ON users.id = user_accounts.user_id')
.joins('INNER JOIN billing_customers ON billing_customers.user_id = users.id')
.joins('INNER JOIN billing_subscriptions ON billing_subscriptions.billing_customer_id = billing_customers.id')
.where('billing_subscriptions.current_end >= ?', current_time)
.where(billing_subscriptions: { basic_billing_subscription_status_id: active_status_ids })
.distinct
.find_in_batches(batch_size: 100) do |batch|
batch.each do |clinic|
# Do not send email if flag is turned off
next if clinic&.feature_enabled?(FeatureFlag::SEND_WEEKLY_CLINIC_EMAIL) == false
# Calculate stats for current week and store in clinic_weekly_stats
CalculateClinicWeeklyStats.perform_now(clinic.id)
Rails.logger.info "Clinic id: #{clinic.id}"
end
GC.start
endPhase 2: Send Emails
# Send out emails for unprocessed stat entries
ClinicWeeklyStat.not_sent.find_in_batches(batch_size: 100) do |batch|
batch.each do |clinic_weekly_stat|
ClinicMailer.weekly_stats(clinic_weekly_stat).deliver_later
clinic_weekly_stat.update!(status: 'SENT')
Rails.logger.info "Clinic Weekly Stat id: #{clinic_weekly_stat.id}"
end
GC.start
endTechnical Implementation
- Respects feature flag:
FeatureFlag::SEND_WEEKLY_CLINIC_EMAIL - Stats are persisted in
clinic_weekly_statstable - Delivery method:
deliver_later(asynchronous via Sidekiq) - Batch size: 100 records per batch
Cron Schedule
# Runs every Monday at 10:00 AM
0 10 * * 1 bundle exec rake db:weekly_clinic_stats_emailSample Output
2025-01-13 10:00:00 +0530
Rake Task Started....
------------------------------------------------------
Collecting Stats
------------------------------------------------------
Clinic id: 12
Clinic id: 13
------------------------------------------------------
Sending Emails
------------------------------------------------------
Clinic Weekly Stat id: 456
Clinic Weekly Stat id: 457
------------------------------------------------------
Rake Task Ended...
======================================================Weekly News Articles
Frequency: Weekly
Recipients: Doctors with active clinic subscriptions
Rake Task: backend/lib/tasks/cron_weekly_notify_published_news.rake
Mailer: DoctorMailer.news_publish
Purpose
Sends the latest published healthcare news articles to doctors:
- Latest 3-6 curated news articles
- Healthcare industry updates
- Medical research highlights
- Professional development content
Eligibility Criteria
- ✅ Doctor is in the mailing list (
MailingListItem.news_list) - ✅ Doctor's clinic has an active subscription
- ✅ Doctor is associated with at least one clinic through
ClinicDoctor
Technical Implementation
# Get current time with timezone for subscription check
current_time = Time.now.in_time_zone(Settings['default_timezone']).utc
active_status_ids = BasicBillingSubscriptionStatus.active_ids
# Skip if no active statuses configured
if active_status_ids.blank?
Rails.logger.warn('No active subscription statuses; skipping published news task')
return
end
# Get doctors with active subscriptions through clinic associations
doctors_with_active_subscriptions = ClinicDoctor.joins(:clinic)
.joins("INNER JOIN user_accounts ON user_accounts.profile_id = clinics.id
AND user_accounts.account_id = #{Account::CLINIC_ID}")
.joins('INNER JOIN users ON users.id = user_accounts.user_id')
.joins('INNER JOIN billing_customers ON billing_customers.user_id = users.id')
.joins('INNER JOIN billing_subscriptions ON billing_subscriptions.billing_customer_id = billing_customers.id')
.where('billing_subscriptions.current_end >= ?', current_time)
.where(billing_subscriptions: { basic_billing_subscription_status_id: active_status_ids })
.distinct
.pluck(:doctor_id)
Rails.logger.info "Found #{doctors_with_active_subscriptions.count} doctors with active subscriptions"
# Filter mailing list to only include doctors with active subscriptions
news_list = MailingListItem.news_list.where(doctor_id: doctors_with_active_subscriptions)
news_list&.find_in_batches(batch_size: batch_size) do |doctors_list|
doctors_list.each do |doc_details|
doc = doc_details.doctor
doctor_name = doc&.short_name || doc_details.doctor_name
is_signed_up = doc.present?
DoctorMailer.news_publish(doc_details.email, doctor_name, article_obj, is_signed_up).deliver_later
end
GC.start
endNews Article Source
Articles are selected from two sources:
-
Weekly News Article List (
WeeklyNewsArticleList.unpublished.first)- If available, uses curated articles from the weekly list
- Marks list as published after sending
-
Latest Published Articles (fallback)
- If no weekly list exists, fetches 6 most recent published articles
- Orders by latest publication date
Delivery Method
- Sent via:
deliver_later(asynchronous via Sidekiq) - Batch size: Configurable via
Settings['search_indexes']['batch_size'] - Memory management: GC after each batch
Cron Schedule
# Runs every week (exact day/time to be configured)
# Example: Every Friday at 9:00 AM
0 9 * * 5 bundle exec rake db:notify_published_newsSample Output
2025-01-17 09:00:00 +0530
Rake Task Started....
------------------------------------------------------
Found 45 doctors with active subscriptions
------------------------------------------------------
Rake Task Ended...
2025-01-17 09:05:00 +0530
======================================================Doctor Relationship Flow
Doctor → ClinicDoctor → Clinic → UserAccount → User → BillingCustomer → BillingSubscriptionNote: Doctors can be associated with multiple clinics. If any of their associated clinics has an active subscription, they will receive the news email.
Database Relationships
Subscription Check Flow
Clinic → UserAccount (profile_id) → User → BillingCustomer → BillingSubscriptionKey Models
BillingSubscription
belongs_to :billing_customer
belongs_to :basic_billing_subscription_status
scope :not_expired, -> { where('current_end >= ?', Time.current) }BillingCustomer
belongs_to :user
has_many :billing_subscriptionsUserAccount
belongs_to :user
belongs_to :clinic, foreign_key: 'profile_id'
# Where account_id = Account::CLINIC_IDRun Tasks Manually
# Monthly review summary
bundle exec rake db:monthly_review_summary
# Weekly clinic stats
bundle exec rake db:weekly_clinic_stats_email
# Weekly news articles
bundle exec rake db:notify_published_news