Skip to content

Birthday & Points Emails

Flow ID: SY-12 | Module(s): job, eshop | Complexity: Medium

Business Overview

Two customer engagement email jobs drive loyalty retention:

  • AdvSentMailToCustomerBirthday: Sends birthday greeting emails with optional bonus loyalty points. Targets customers whose birthday falls on the current date, with a configurable repeat interval to avoid sending too frequently.
  • AdvSentMailToCustomerRemainingPoints: Reminds customers about their available loyalty points balance, targeting those with points above a minimum threshold who have recent order activity. Encourages point redemption and repeat purchases.

Both jobs track their sends via shop_customer_campaign to enforce configurable repeat intervals.

Architecture

AdvSentMailToCustomerBirthday              AdvSentMailToCustomerRemainingPoints
  (gated by EMAIL_FOR_BIRTHDAY)              (gated by POINT_SYSTEM + EMAIL_FOR_TOTAL_POINTS)
         |                                            |
         v                                            v
  customer_model                               customer_model
  getCustomersForBirthday()                    getCustomersForRemainingPoints()
         |                                            |
         v                                            v
  For each customer:                           For each customer:
    adv_mailer::sentBirthdayWishes()             adv_mailer::sentRemainingPoints()
    + optional loyalty::savePointsToCustomer()   (includes point-to-cash conversion)
    + customer_campaign_model::                  + customer_campaign_model::
        saveCustomerCampaign(BIRTHDAY)               saveCustomerCampaign(POINTS_REMAINING)

Key Components

ComponentPathRole
AdvSentMailToCustomerBirthdayecommercen/job/libraries/AdvSentMailToCustomerBirthday.phpJob: birthday email + optional points
AdvSentMailToCustomerRemainingPointsecommercen/job/libraries/AdvSentMailToCustomerRemainingPoints.phpJob: points reminder email
Adv_customer_modelecommercen/eshop/models/Adv_customer_model.phpModel: birthday/points customer queries
Adv_customer_campaign_modelecommercen/eshop/models/Adv_customer_campaign_model.phpModel: campaign tracking (prevents duplicates)
Adv_mailerapplication/models/Adv_mailer.phpMailer: birthday wishes and points reminder emails
Adv_loyaltyecommercen/libraries/Adv_loyalty.phpLibrary: point calculation and cash conversion
Client stubsapplication/modules/job/libraries/SentMailToCustomerBirthday.php, SentMailToCustomerRemainingPoints.phpEmpty extension points

Code Flow

AdvSentMailToCustomerBirthday

  1. Feature gate: Checks EMAIL_FOR_BIRTHDAY.IS_ENABLED registry value. Returns immediately if disabled.
  2. Points calculation: If POINT_SYSTEM.IS_ENABLED is true, reads EMAIL_FOR_BIRTHDAY.POINTS_TO_ADD for bonus birthday points. Otherwise, pointsToAdd = 0.
  3. Customer query: customer_model->getCustomersForBirthday($repeat):
    • Reads EMAIL_FOR_BIRTHDAY.REPEAT registry value (default: 12 months).
    • Builds exclusion subquery: customers who already received a BIRTHDAY campaign within the repeat interval.
    • Query conditions: is_guest = false, is_sanitized = false.
    • Birthday match: Uses DATE_ADD(birthdate, INTERVAL ... YEAR) to compute next birthday and HAVING next_birthday = TODAY.
  4. Per-customer processing:
    • Email: adv_mailer->sentBirthdayWishes($customerId, $pointsToAdd):
      • Fetches full customer record.
      • If pointsToAdd > 0: Awards bonus points via loyalty->savePointsToCustomer() and includes point/cash values in email data.
      • Sends email using birthdayWishes template with personalized subject (EMAIL_SUBJECTS.BIRTHDAY_WISHES with customer name).
    • Tracking: customer_campaign_model->saveCustomerCampaign($customerId, BIRTHDAY) inserts a record with current timestamp.

AdvSentMailToCustomerRemainingPoints

  1. Feature gates (two checks):
    • POINT_SYSTEM.IS_ENABLED must be true.
    • EMAIL_FOR_TOTAL_POINTS.IS_ENABLED must be true.
  2. Configuration: Reads from EMAIL_FOR_TOTAL_POINTS registry group:
    • TOTAL_POINTS: Minimum points threshold (returns empty if <= 0).
    • INTERVAL: Months of recent order activity required (0 = no filter).
    • INTERVAL_REPEAT: Months before sending again to the same customer.
  3. Customer query: customer_model->getCustomersForRemainingPoints($fromPoints, $interval, $intervalRepeat):
    • Conditions: is_guest = false, is_sanitized = false, total_points > $fromPoints.
    • Excludes customers with orders within the interval period (targets inactive customers with points to spend).
    • Excludes customers who received a POINTS_REMAINING campaign within the repeat interval.
  4. Per-customer processing:
    • Email: adv_mailer->sentRemainingPoints($customerId):
      • Fetches customer record and calculates spendable points and cash equivalent.
      • Sends email using remainingPoints template with registry-configured subject (EMAIL_SUBJECTS.REMAINING_POINTS).
    • Tracking: customer_campaign_model->saveCustomerCampaign($customerId, POINTS_REMAINING).

Data Model

Tables

TableRelevant ColumnsRole
shop_customerid, birthdate, total_points, is_guest, is_sanitizedCustomer records with birthday and point balance
shop_customer_campaigncustomer_id, campaign_type, entry_datetimeCampaign send tracking for repeat interval enforcement
shop_ordercustomer_id, entry_datetimeOrders; used to filter active customers for points reminders

Campaign Types (CUSTOMER_CAMPAIGN_TYPE constant)

TypeIDUsed By
POINTS_REMAINING1AdvSentMailToCustomerRemainingPoints
BIRTHDAY2AdvSentMailToCustomerBirthday

shop_customer_campaign Schema

ColumnTypeDescription
customer_idintFK to shop_customer
campaign_typeintCampaign type from CUSTOMER_CAMPAIGN_TYPE constant
entry_datetimedatetimeTimestamp when the campaign email was sent

Configuration

Registry Settings

Birthday Email

GroupKeyDefaultDescription
EMAIL_FOR_BIRTHDAYIS_ENABLED--Master toggle for birthday emails
EMAIL_FOR_BIRTHDAYPOINTS_TO_ADD--Bonus loyalty points awarded on birthday
EMAIL_FOR_BIRTHDAYREPEAT12Months between birthday emails for the same customer
EMAIL_SUBJECTSBIRTHDAY_WISHES--Localized email subject (supports %s for customer name)

Remaining Points Email

GroupKeyDefaultDescription
POINT_SYSTEMIS_ENABLED--Master toggle for loyalty points system
EMAIL_FOR_TOTAL_POINTSIS_ENABLED--Toggle for remaining points emails
EMAIL_FOR_TOTAL_POINTSTOTAL_POINTS--Minimum points threshold to qualify
EMAIL_FOR_TOTAL_POINTSINTERVAL--Months of order inactivity required (0 = any)
EMAIL_FOR_TOTAL_POINTSINTERVAL_REPEAT--Months before re-sending to same customer
EMAIL_SUBJECTSREMAINING_POINTS--Localized email subject

Email Templates

JobTemplate ViewMailer Configuration
BirthdaybirthdayWishesSender: EMAIL_SHOP_MAILER, Config: REMAINING_POINTS
Remaining PointsremainingPointsSender: EMAIL_SHOP_MAILER, Config: REMAINING_POINTS

Job Options

Both jobs accept no options. All behavior is controlled via registry settings.

Client Extension Points

  • Job override: Create SentMailToCustomerBirthday or SentMailToCustomerRemainingPoints in application/modules/job/libraries/ extending the Adv* base class.
  • Customer model override: Override getCustomersForBirthday() or getCustomersForRemainingPoints() to add custom eligibility criteria (e.g., membership tier, opt-in flag).
  • Mailer override: Override sentBirthdayWishes() or sentRemainingPoints() in Adv_mailer to customize email content, add coupon codes, or integrate with third-party email services.
  • Loyalty override: Override Adv_loyalty to customize point-to-cash conversion rates or add birthday multipliers.
  • Campaign types: Extend CUSTOMER_CAMPAIGN_TYPE constant and Adv_customer_campaign_model to add new campaign types.

Business Rules

Birthday Emails

  1. Feature gate: The job is a complete no-op when EMAIL_FOR_BIRTHDAY.IS_ENABLED is false.
  2. Registered customers only: Guests (is_guest=true) and sanitized accounts (is_sanitized=true) are excluded.
  3. Birthday matching: Uses MySQL date arithmetic to find customers whose next birthday falls on today's date, handling year rollover correctly.
  4. Repeat interval: Default 12 months between birthday emails. Tracked via shop_customer_campaign with type BIRTHDAY. Setting to 0 effectively disables the repeat prevention.
  5. Conditional points: Birthday bonus points are only awarded when POINT_SYSTEM.IS_ENABLED is true AND POINTS_TO_ADD > 0. Points are persisted via the loyalty library before the email is sent.
  6. Cash display: If points are awarded, the email includes both the point amount and the equivalent cash value (using the default currency).
  7. Personalized subject: The email subject line includes the customer's first name via vsprintf().

Remaining Points Emails

  1. Double gate: Requires both POINT_SYSTEM.IS_ENABLED and EMAIL_FOR_TOTAL_POINTS.IS_ENABLED to be true.
  2. Minimum threshold: Only targets customers with total_points > TOTAL_POINTS setting. If threshold is 0 or negative, no customers are processed.
  3. Inactivity targeting: When INTERVAL > 0, excludes customers who have placed orders within that many months. This targets customers who may have forgotten about their points.
  4. Repeat prevention: INTERVAL_REPEAT controls how many months must pass before the same customer receives another reminder, tracked via shop_customer_campaign with type POINTS_REMAINING.
  5. Cash conversion: The email displays both the spendable points and their cash equivalent, calculated via loyalty->calcPointsToSpend() and loyalty->pointsToCash().