Skip to content

Delivery Delay Notifications ​

Flow ID: SY-15 | Module(s): job, eshop | Complexity: Medium Last Updated: 2026-09-29

Business Overview ​

When orders experience shipping delays, the platform proactively notifies customers via email before they contact support. The system identifies orders that have been pending shipment for too long (2+ or 3+ days depending on configuration) and sends an apology/information email. This builds customer trust by acknowledging delays transparently.

Two operating modes:

  1. Standard delay (withWarningDelay=false): Targets orders 2+ days old without a delivery warning flag
  2. Extended delay (withWarningDelay=true): Targets orders 3+ days old that have the delivery warning flag set

The delivery_warning flag on orders is computed automatically by Adv_order_model::setDeliveryWarningMessage() (ecommercen/eshop/models/Adv_order_model.php:1740) from live stock vs. cart quantity at checkout/order-creation time -- it returns true when on-hand stock is less than the ordered quantity (or stock is exhausted and negative stock is allowed), giving those orders an extra day before the automated notification fires. It is called at customer checkout (ecommercen/eshop/controllers/Adv_order.php:959) and at admin order create/edit (ecommercen/eshop/controllers/Adv_orders_admin.php:552, :1162, :3073); there is no manual admin input for it.


Architecture ​

AdvSendEmailForDeliveryDelay (JobCommand)
  |
  +--> order_model::sendMailForDelayOrderToCustomers($withWarningDelay)
         |
         +--> Query eligible orders:
         |      shop_order JOIN shop_customer
         |      status NOT IN (CANCELED, PENDING)
         |      store_id IS NULL
         |      delivery_warning = $withWarningDelay
         |      delivery_warning_msg_status = 0 (excludes sent and admin-suppressed)
         |      invoised_date_time IS NULL
         |      canceled_date IS NULL
         |      entry_datetime >= 2019-01-01
         |      entry_datetime <= (now - 2 days) or (now - 3 days)
         |
         +--> For each order:
                adv_mailer::sendCustomerInformDelay($order)
                  |
                  +--> order_model::setDeliveryWarningMsgStatus(1, $orderSerial)
                  +--> send email via orderInformDelay template (view: apologize_shipping_delay.php, added #2/#81 in 4.118.0)

Job Classes ​

ClassFilePurpose
AdvSendEmailForDeliveryDelayecommercen/job/libraries/AdvSendEmailForDeliveryDelay.phpBase job implementation
SendEmailForDeliveryDelayapplication/modules/job/libraries/SendEmailForDeliveryDelay.phpClient-overridable subclass

Supporting Classes ​

ClassFileRole
Adv_order_modelecommercen/eshop/models/Adv_order_model.phpOrder query and status update logic
Adv_mailerapplication/models/Adv_mailer.phpEmail dispatch with delay template

Code Flow ​

1. Job Initialization ​

The job is lightweight -- no constructor, just inline model loading in executeCommand():

executeCommand:
  parse withWarningDelay option (bool, defaults to false)
  load eshop/order_model
  load adv_mailer
  call order_model->sendMailForDelayOrderToCustomers($withWarningDelay)

2. Calculate Cutoff Date ​

In sendMailForDelayOrderToCustomers():

date = now - 2 days
if withWarningDelay:
    date = now - 3 days (additional -1 day)
toDate = date formatted as Y-m-d

3. Query Eligible Orders ​

The method builds a query with these conditions:

ConditionPurpose
status NOT IN ('CANCELED', 'PENDING')Only process active, accepted orders
store_id IS NULLExclude store-pickup orders (those have different logistics)
delivery_warning = $withWarningDelayMatch the delay tier (standard vs. flagged)
delivery_warning_msg_status = 0Only orders not yet notified (0=unsent; 1=sent; 3=admin opted out); excludes both sent and admin-suppressed
invoised_date_time IS NULLExclude already-invoiced orders (they shipped)
canceled_date IS NULLExclude canceled orders
entry_datetime >= '2019-01-01'Safety floor to exclude very old legacy data
entry_datetime <= $toDateOnly orders older than the threshold

Results are ordered by entry_datetime ASC (oldest first) and include customer email and language from shop_customer.

4. Send Notifications ​

For each eligible order, calls adv_mailer->sendCustomerInformDelay($order):

  1. Validates that the order has order_serial and mail properties
  2. Marks processed first: calls order_model->setDeliveryWarningMsgStatus(1, $orderSerial) before sending -- this prevents reprocessing even if the email fails
  3. Sends email using:
    • Template: orderInformDelay
    • Data: the full order object
    • Subject: EMAIL_SUBJECTS.CUSTOMER_INFORM_DELAY (supports %s for order serial)
    • SMTP profile: EMAIL_SHOP_MAILER

Data Model ​

Primary Table: shop_order ​

ColumnTypeRole
idintPK
order_serialvarcharHuman-readable order number
statusvarcharOrder status
entry_datetimedatetimeWhen the order was placed
delivery_warningbooleanAuto-computed at checkout/order-creation time when stock is insufficient for the ordered quantity (gives extra day); set via Adv_order_model::setDeliveryWarningMessage() (ecommercen/eshop/models/Adv_order_model.php:1740)
delivery_warning_msg_statustinyint(1)Tri-state: 0=unsent, 1=sent, 3=admin opted out of informing customer (ecommercen/eshop/controllers/Adv_orders_admin.php:1951, setter: ecommercen/eshop/models/Adv_order_model.php:3372)
store_idint/nullNULL = courier delivery; non-null = store pickup
invoised_date_timedatetime/nullWhen invoiced (NULL = not yet shipped)
canceled_datedatetime/nullWhen canceled (NULL = not canceled)
customer_idintFK to shop_customer
TableRelationshipPurpose
shop_customercustomer_id -> idCustomer email (mail) and language (lang)

Configuration ​

Job Scheduling (application/config/jobs.php) ​

Two entries are typically configured -- one for each delay tier:

php
// Standard delay (2+ days):
// ['command' => 'SendEmailForDeliveryDelay', 'schedule' => '0 6 * * *', 'graceTime' => 300, 'retryTimes' => 3],

// Extended delay (3+ days, for flagged orders):
// ['command' => 'SendEmailForDeliveryDelay', 'schedule' => '0 6 * * *', 'graceTime' => 300, 'retryTimes' => 3, 'options' => ['withWarningDelay' => 'yes']],

Commented out by default (client opt-in).

Job Options ​

OptionTypeRequiredDescription
withWarningDelayboolNoWhen true, targets orders 3+ days old with delivery_warning=true. Defaults to false (2+ days, no warning flag)

Registry Settings ​

GroupKeyPurpose
EMAIL_SUBJECTSCUSTOMER_INFORM_DELAYEmail subject template (supports %s for order serial)
EMAIL_SHOP_MAILER(group)SMTP configuration for outbound shop emails

Email Templates ​

Template keyView fileDescription
orderInformDelayapplication/views/main/mail/apologize_shipping_delay.phpApology/information email about the shipping delay. View file added in #2/#81 (4.118.0); renders $order->order_serial via lang keys eshop.front.mail.shipping.delay.{title,message,reassurance,thanks}

Client Extension Points ​

  1. Override the job class: Create SendEmailForDeliveryDelay in application/modules/job/libraries/ extending AdvSendEmailForDeliveryDelay. Override executeCommand() to change delay thresholds or add custom filtering.

  2. Override the order model: Override sendMailForDelayOrderToCustomers() in Order_model to change the query criteria (e.g., exclude specific payment methods, adjust the date threshold, add status filters).

  3. Override the mailer: Extend Adv_mailer in application/models/ to customize sendCustomerInformDelay() behavior (e.g., add admin CC, change template).

  4. Override email template: Place a custom orderInformDelay view file in application/views/ to change the email content.

  5. Dual-tier scheduling: Configure both standard and extended delay jobs with different schedules for granular control.


Business Rules ​

RuleDescription
Two delay tiersStandard (2+ days) and extended (3+ days with delivery_warning flag)
One notification per orderdelivery_warning_msg_status tri-state (0=unsent, 1=sent, 3=admin opted out) prevents duplicate notifications; query filters on status=0, automatically excluding admin-suppressed orders
Courier orders onlystore_id IS NULL excludes store-pickup orders from delay notifications
Active orders onlyExcludes CANCELED and PENDING statuses, as well as already-invoiced and canceled orders
Mark-before-sendThe status flag is set before the email is sent, preventing reprocessing on partial failures
Legacy data floorOrders before 2019-01-01 are excluded via a hardcoded date floor
Per-client opt-inBoth job variants are commented out by default

Known Issues & Security Gaps ​

Resolved (4.118.0):

  1. Delivery-delay email dispatch fatally failed (view-not-found) — Prior to issue #2/#81 (4.118.0), every delivery-delay dispatch fatally failed because the backing view application/views/main/mail/apologize_shipping_delay.php did not exist. The view now exists and renders $order->order_serial via lang keys eshop.front.mail.shipping.delay.{title,message,reassurance,thanks} (added to all 8 adv_external_lang.php files). As of 4.118.0, the flow is fully functional.