Skip to content

Order Status Email Notifications

Flow ID: SY-02 | Module(s): job, eshop | Complexity: Medium Last Updated: 2026-04-04

Business Overview

When an order transitions to INVOICED or SENT status, the platform automatically notifies the customer via email. For shipped orders, an SMS notification is also dispatched through a configurable provider. There are three distinct notification paths depending on the fulfillment model:

  1. Invoiced orders (warehouse dispatch) -- email only, triggered when the order reaches INVOICED status.
  2. Shipped orders (courier delivery) -- email plus SMS, triggered when the order reaches SENT status with a courier assigned.
  3. Store-pickup orders -- email plus SMS, triggered when the order reaches SENT status at a physical store location.

All three jobs are per-client opt-in (commented out by default in jobs.php) because not every merchant uses the same notification strategy.

Architecture

AdvSendEmailBasedOnSentStatus          (email on INVOICED)
AdvSendEmailAndSMSBasedOnSentStatus    (email+SMS on SENT, courier)
AdvSendEmailAndSMSBasedOnFromStoreStatus (email+SMS on SENT, store pickup)
  |
  +--> order_model::getRecords()       query eligible orders
  +--> adv_mailer::order_has_been_updated()  /  order_is_on_store()
  +--> sendSmsForOrder() / sendSmsForOrderId()   (SMS helpers)
  +--> order_model::update_order()     mark is_email_sent = true

Each job class lives in ecommercen/job/libraries/ and has a thin client-overridable subclass in application/modules/job/libraries/.

Job Classes

ClassFileStatus TriggerChannels
AdvSendEmailBasedOnSentStatusecommercen/job/libraries/AdvSendEmailBasedOnSentStatus.phpINVOICEDEmail
AdvSendEmailAndSMSBasedOnSentStatusecommercen/job/libraries/AdvSendEmailAndSMSBasedOnSentStatus.phpSENT (courier)Email + SMS
AdvSendEmailAndSMSBasedOnFromStoreStatusecommercen/job/libraries/AdvSendEmailAndSMSBasedOnFromStoreStatus.phpSENT (store)Email + SMS

Client Overridable Classes

ClassFile
SendEmailBasedOnSentStatusapplication/modules/job/libraries/SendEmailBasedOnSentStatus.php
SendEmailAndSMSBasedOnSentStatusapplication/modules/job/libraries/SendEmailAndSMSBasedOnSentStatus.php
SendEmailAndSMSBasedOnFromStoreStatusapplication/modules/job/libraries/SendEmailAndSMSBasedOnFromStoreStatus.php

Code Flow

SY-02a: AdvSendEmailBasedOnSentStatus (Invoiced Orders)

  1. Query eligible orders via order_model->getRecords() with conditions:
    • is_email_sent = false
    • status = 'INVOICED'
    • store_id IS NULL (excludes store-pickup orders)
    • gtcode != '' (must have a tax document code)
  2. For each order, send email via adv_mailer->order_has_been_updated($orderId, $data):
    • Loads order + customer data
    • Builds encrypted order tracking link
    • Resolves transporter info for courier display
    • Sends via the orderUpdate email template with subject from EMAIL_SUBJECTS.ORDER_UPDATE registry
    • Attaches invoice PDF if available (for INVOICED/SENT/PAID_SENT statuses)
  3. Mark processed: sets is_email_sent = true on the order record.

SY-02b: AdvSendEmailAndSMSBasedOnSentStatus (Shipped Orders)

  1. Validate options: requires provider option (SMS provider name). Exits silently if missing.
  2. Query eligible orders with conditions:
    • is_email_sent = false
    • status = 'SENT'
    • store_id IS NULL (courier delivery only)
    • gtcode IS NOT NULL AND gtcode != ''
    • transport_id IS NOT NULL (courier must be assigned)
  3. For each order:
    • Send email via adv_mailer->order_has_been_updated()
    • If sms_status == 1 (SMS pending), dispatch SMS via sendSmsForOrder():
      • Resolves transporter from cache
      • Dispatches through the configured provider
      • Updates sms_status based on provider response (see SMS status codes below)
    • Sets is_email_sent = true

SY-02c: AdvSendEmailAndSMSBasedOnFromStoreStatus (Store Pickup)

  1. Validate options: requires provider option.
  2. Query eligible orders with conditions:
    • is_email_sent = false
    • status = 'SENT'
    • store_id IS NOT NULL (store-pickup only)
    • sent_date_time <= today 17:00:00 (only notify after the cutoff time)
  3. For each order:
    • Send email via adv_mailer->order_is_on_store():
      • Loads store details for the pickup location
      • Uses orderOnStore email template
      • Subject from EMAIL_SUBJECTS.ORDER_ON_STORE registry
    • If sms_status == 1, dispatch SMS via sendSmsForOrderId() (simpler variant without transporter)
    • Sets is_email_sent = true

Data Model

Primary Table: shop_order

ColumnTypeRole
idintPK
order_serialvarcharHuman-readable order number
statusvarcharOrder status (PENDING, PAID, INVOICED, SENT, etc.)
is_email_sentbooleanFlag -- set to true after notification sent
sms_statusintSMS delivery state (see below)
store_idint/nullNULL = courier delivery; non-null = store pickup
transport_idint/nullFK to transporter/courier
gtcodevarcharTax document code (invoice number)
sent_date_timedatetimeWhen the order was marked as shipped
customer_idintFK to customer
invoice_pdfvarcharFilename of generated invoice PDF

SMS Status Codes

ValueMeaning
1Pending (customer opted in, SMS not yet sent)
2Sent successfully (Plivo)
3Failed to send
4Sent successfully (Bulker/Yuboto Omni/Omni Messaging)
otherProvider-specific callback status (Yuboto, Routee)

Configuration

Job Scheduling (application/config/jobs.php)

All three jobs are commented out by default (per-client opt-in):

php
// ['command' => 'SendEmailBasedOnSentStatus', 'schedule' => '0 21 * * *', 'graceTime' => 300, 'retryTimes' => 3],
// ['command' => 'SendEmailAndSMSBasedOnSentStatus', 'schedule' => '0 21 * * *', 'graceTime' => 300, 'retryTimes' => 3, 'options' => ['provider' => 'yuboto']],
// ['command' => 'SendEmailAndSMSBasedOnFromStoreStatus', 'schedule' => '0 21 * * *', 'graceTime' => 300, 'retryTimes' => 3, 'options' => ['provider' => 'yuboto']],

Default schedule: daily at 21:00. Queue: core.

SMS Providers

The provider option accepts one of:

ProviderCallbackStatus on success
yubotowebrun/yuboto_callback/{orderId}Callback-driven
yuboto_omniwebrun/yubotoOmniCallback/{orderId}4
routeewebrun/routee_callback/{orderId}Callback-driven
routeeViberwebrun/routee_callback_viber/{orderId}Callback-driven
plivoNone2
bulkerNone4
omni_messagingNone4

Registry Settings

GroupKeyPurpose
EMAIL_SUBJECTSORDER_UPDATEEmail subject template (supports %s for order serial)
EMAIL_SUBJECTSORDER_ON_STOREStore-pickup email subject template
COMPANY_PHONEESHOPCompany phone included in email body

Email Templates

Template keyUsed by
orderUpdateINVOICED and SENT notifications
orderOnStoreStore-pickup notifications

SMTP Configuration

Both email methods use the EMAIL_SHOP_MAILER SMTP profile.

Client Extension Points

  1. Override the job class: Create a class in application/modules/job/libraries/ extending the Adv* base class. Override getQueryOptions() to change which orders are selected, or override executeCommand() to add custom logic.

  2. Override the mailer: Extend Adv_mailer in application/models/ to customize email content or add additional recipients.

  3. Override email templates: Place custom email view files in application/views/ to override the default orderUpdate and orderOnStore layouts.

  4. Change SMS provider: Set the options.provider value in jobs.php to switch between supported SMS gateways.

  5. Adjust schedule: Uncomment and modify the cron expression in jobs.php to match the client's business hours.

Business Rules

RuleDescription
One-shot deliveryEach order is notified exactly once; is_email_sent flag prevents re-processing
Store cutoff timeStore-pickup notifications are only sent for orders shipped before 17:00 on the current day
SMS opt-inSMS is only sent when sms_status == 1 (customer consented)
Tax code requiredINVOICED and SENT notifications require a non-empty gtcode
Courier requiredSENT (courier) notifications require a non-null transport_id
Invoice attachmentPDF invoice is attached to the email when available for INVOICED/SENT/PAID_SENT statuses
Per-client activationJobs are commented out by default; merchants enable the ones matching their fulfillment model