Life Truck Delivery Date Confirmation
Before a Life truck delivery goes out, we text the customer the date and ask them to confirm it. If they say yes we lock it in; if they say no we offer the next dates that actually have room on a truck and take their pick; if they never answer we give the date back and start again. Only deliveries on our own trucks go through this, and any delivery that needs a person rather than an automation is handed to the fulfilment team with the whole conversation attached.
Rules & IDs
| Rule ID | Rule | Reading |
|---|---|---|
LI-BL-FUL-005 |
Which deliveries we ask about | Business logic |
LI-BL-FUL-006 |
One conversation per customer per day | Business logic |
LI-BL-FUL-007 |
Two attempts, then stop asking | Business logic |
LI-BL-FUL-008 |
Authority to Leave needs no reply | Business logic |
LI-BL-FUL-009 |
Giving back an unconfirmed date | Business logic |
LI-BL-FUL-010 |
Reading the customer's reply | Business logic |
LI-BL-FUL-011 |
Confirming the date we offered | Business logic |
LI-BL-FUL-012 |
Offering the next available dates | Business logic |
LI-BL-FUL-013 |
Accepting the date they chose | Business logic |
LI-BL-FUL-014 |
Handing back to the team | Business logic |
LI-BL-FUL-015 |
Replies after the conversation closes | Business logic |
LI-BL-FUL-016 |
The reminder before delivery day | Business logic |
LI-BL-FUL-017 |
Escalating a delivery we cannot settle | Business logic |
| What | Name in the system | |
|---|---|---|
| S1.1 — Outbound confirmation SMSGitHub repository | lifeinteriors/lifedeldateconfirm-s11-outbound @ 85d4a74 — lifedeldateconfirm-s11-outbound | open at 85d4a74 |
| S1.2 — Inbound reply handlingGitHub repository | lifeinteriors/lifedeldateconfirm-s12-inbound @ e7e44ef — lifedeldateconfirm-s12-inbound | open at e7e44ef |
| S1.3 — Pre-delivery reminderGitHub repository | lifeinteriors/lifedeldateconfirm-s13-reminder @ 7a55ec6 — lifedeldateconfirm-s13-reminder | open at 7a55ec6 |
| S2.2 — Kustomer escalationGitHub repository | lifeinteriors/lifedeldatefailed-s22-kustomer @ 1750369 — lifedeldatefailed-s22-kustomer | open at 1750369 |
| Availability engine — sheet and CBM capacity into SupabaseGitHub repository | lifeinteriors/courier-schedule-sync @ 09b8de6 — courier-schedule-sync | open at 09b8de6 |
| Saved search — initial confirmation feedNetSuite saved search | customsearch7249 needs confirming | open search 7249 |
| Saved search — first follow-up feedNetSuite saved search | customsearch7326 needs confirming | open search 7326 |
| Saved search — reschedule feedNetSuite saved search | customsearch7327 needs confirming | open search 7327 |
| Saved search — pre-delivery reminder feedNetSuite saved search | customsearch7506 needs confirming | open search 7506 |
| Saved search — escalation feedNetSuite saved search | customsearch7250 needs confirming | open search 7250 |
The NetSuite links use the account-neutral host, which redirects a signed-in user to this account. Ids and names are exact, so a search stays findable by name even if a link does not resolve.
Three readings of the same rules. Nothing is duplicated between them. Printing gives you the business reading followed by the diagrams.
What it does. It runs the conversation that turns an intended delivery date into a confirmed one. It texts the customer the date, reads their reply, and writes the answer back against the order so the rest of the fulfilment chain knows whether the delivery is agreed. When the customer does not want the date, it offers alternatives that are genuinely available and books the one they choose. When nothing can be settled automatically, it stops and puts the delivery in front of a person.
Why it exists. A Life truck delivery that turns up when nobody is home is the single most expensive failure in fulfilment: the truck, the driver, the slot on the run and the second attempt are all lost, and the customer is the one who has to chase it. Confirming the date first turns that cost into a text message. It also stops the older failure mode, where a delivery nobody had agreed to sat on a truck run and blocked capacity that a confirmed delivery could have used.
Who it affects. Customers waiting on a Life truck delivery notice first — they receive the messages. The Fulfilment Team feels it next: every delivery this cannot settle lands in their queue, and every date it releases goes back into truck scheduling. Customer Care picks up the customers who reply after a conversation has closed, or who reply with something the automation cannot read.
When it runs. The outbound side runs on a schedule and works backwards through the conversation — last chances first, then follow-ups, then deliveries nobody has been asked about yet. A delivery becomes eligible once it is going on a Life truck and has a delivery date set. Replies are handled the moment they arrive, not on a schedule. The reminder goes out about three days before the delivery date, and the escalation sweep runs over deliveries that have already gone wrong.
How it decides.
- Only Life truck deliveries with a date set are asked about at all, and any delivery a person has marked as manually handled is left alone entirely.
- Orders going to the same customer on the same day are treated as one delivery and get one message, not one per order.
- A customer who has authorised the driver to leave the delivery is told the date rather than asked to confirm it — unless part of the delivery needs the customer present, in which case they are asked like anyone else.
- A customer gets two chances to reply. After the second unanswered message the date is given back and the delivery goes for rescheduling.
- A "no" is never treated as a dead end: we come back with the next dates that have real capacity and let the customer pick one.
- Anything the automation cannot read with confidence — an ambiguous reply, a choice we never offered, a delivery with no available dates — goes to a person rather than being guessed at.
Outcomes. A delivery leaves this process in one of six states: confirmed on the original date; confirmed on a date the customer chose instead; released for rescheduling because nobody replied; waiting on the Life Interiors team because the reply needed a human; left untouched because someone marked it for manual handling; or escalated to the fulfilment team because it has been rescheduled too many times or we have no usable phone number for it.
Which deliveries we ask about LI-BL-FUL-005
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | The delivery is going on a Life truck and has a delivery date set | It enters the confirmation conversation | These are the only deliveries where our own capacity is at stake and a failed attempt costs us a truck slot |
| 2 | Someone has marked the delivery as manually handled | It is left completely alone — no message, no field written | A person has taken ownership; automation writing over them would undo their work |
| 3 | The delivery is not on a Life truck | It never enters this process | Third-party couriers run their own notification and booking |
One conversation per customer per day LI-BL-FUL-006
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | A customer has several orders arriving the same day, on the same terms | They get one message covering all of them, and one reply settles them all | Three texts about one van turning up reads as a fault, and three separate answers would let the orders drift apart |
| 2 | The same customer has orders on two different days | Each day is its own conversation | They are two deliveries, and the customer may want one and not the other |
| 3 | Two orders for the same day differ on whether the driver may leave them | They are handled as separate conversations | One can be left on the doorstep and one cannot, so they cannot share an answer |
Two attempts, then stop asking LI-BL-FUL-007
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | Nobody has been asked about this delivery yet | Send the first request and record that one attempt has been made | The conversation has to start somewhere, and the record is what stops it starting twice |
| 2 | One attempt has been made and no reply has come | Send a second, more direct request and record the second attempt | People miss a text; one reminder is reasonable, and it is cheap next to a failed delivery |
| 3 | Two attempts have been made and no reply has come | Stop asking, tell the customer the delivery will be rescheduled, and release the date | Asking a third time does not produce an answer, and holding an unconfirmed slot costs capacity |
| 4 | The delivery has a confirmation state we do not recognise | Raise it for review rather than guessing which attempt it is on | Guessing could mean texting a customer who already answered |
Authority to Leave needs no reply LI-BL-FUL-008
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | The customer authorised the driver to leave the delivery, and nothing in the delivery needs them present | Tell them the date, expect no reply, and mark the delivery agreed | They have already told us they do not need to be home, so asking them to confirm adds nothing |
| 2 | The customer authorised leaving, but part of the delivery needs the customer present | Ask them to confirm as normal | A two-man or full-service delivery cannot be left on a doorstep, so the authority does not apply |
Giving back an unconfirmed date LI-BL-FUL-009
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | Two attempts have gone unanswered | Clear the delivery date, the dispatch date, the courier approval and the reason it was changed, so the delivery looks unscheduled again | This is deliberately how the order falls back into shipping automation for a fresh date — leaving any one of them set would keep it out |
| 2 | The date is released | Also clear the confirmation state itself | The next date has not been asked about yet, so any leftover state would put the delivery in the wrong queue |
| 3 | The date is released | Still tell the customer it is being rescheduled | Silence after two messages reads as the order being lost |
Reading the customer's reply LI-BL-FUL-010
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | The reply clearly agrees | Treat it as a yes | Customers say yes in many ways and we should not fail them on wording |
| 2 | The reply clearly declines or asks to change the date | Treat it as a no and move to offering alternatives | Declining is the start of rescheduling, not a dead end |
| 3 | We asked the customer to pick from a numbered list and they reply | Only a number we actually offered counts; anything else is treated as needing a person | Acting on a number we never offered would book a delivery onto the wrong run |
| 4 | The reply cannot be read with confidence | Treat it as needing a person, never as a yes | A wrong yes puts a truck on the road; a wrong hand-off costs someone a phone call |
| 5 | The customer says no again after we already offered dates | Hand it to a person rather than offering the same list again | They have seen the options and none worked, so repeating them wastes everyone's time |
Confirming the date we offered LI-BL-FUL-011
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | The customer agrees to the date | Mark the delivery confirmed and acknowledge it | The delivery is now agreed and the rest of fulfilment can rely on it |
| 2 | The delivery is confirmed | Close the conversation | There is nothing further to ask, and leaving it open would let a stray reply reopen it |
Offering the next available dates LI-BL-FUL-012
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | The customer declines the date | Offer up to three of the next dates that have real truck capacity for their address and volume | Offering dates we cannot actually deliver on just moves the failure later |
| 2 | Dates are offered | Leave the delivery's confirmation state untouched until they pick | Nothing has been agreed yet, and marking it early would misreport the delivery as settled |
| 3 | We hold no volume or address for the delivery, or the availability engine returns nothing | Do not offer anything — hand it to a person | An offer we cannot honour is worse than a phone call |
Accepting the date they chose LI-BL-FUL-013
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | The customer picks one of the offered dates | Move the delivery to that date, book it onto the run that had the capacity, and confirm it | The offer was made against a specific run, so the booking has to land on that run to be real |
| 2 | The chosen run travels by linehaul | Record the linehaul date too | The goods have to travel to the delivery city before the delivery date |
| 3 | The delivery is rebooked | Tell the customer the new date is confirmed | They chose from a list and need to know it landed |
Handing back to the team LI-BL-FUL-014
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | The reply cannot be read, or picks something we never offered | Mark the delivery as needing the Life Interiors team and stop automating it | The automation has run out of confident moves |
| 2 | No delivery dates can be offered | Mark it the same way | The customer has said no and we have nothing to offer them |
| 3 | A delivery is handed back | Leave the date as it stands | Releasing a date the customer may still want would make the follow-up call harder |
Replies after the conversation closes LI-BL-FUL-015
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | A customer replies after their delivery is already settled | Record what they said, tell them a person will pick it up, and raise it for the team | Their delivery is agreed and a late reply must not silently re-open it |
| 2 | A late reply arrives | Change nothing on the order | The automation cannot tell whether they are confirming, cancelling or asking a question, and a person can |
The reminder before delivery day LI-BL-FUL-016
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | A confirmed delivery is about three days away | Send one reminder that the date is locked and routes are being finalised | It is the last useful moment for a customer to tell us something has changed |
| 2 | A reminder has already gone out today | Do not send another | A duplicate reminder reads as a system fault |
| 3 | The reminder goes out | Change nothing on the order, and expect no reply | It is informational; the delivery is already agreed |
Escalating a delivery we cannot settle LI-BL-FUL-017
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | A delivery's date has been changed three or more times | Stop automating it and raise it for the fulfilment team | Three reschedules means something is wrong that a text message will not fix |
| 2 | We have no mobile number, or only a landline | Raise it for the fulfilment team | The whole process is SMS-based, so there is no way to ask |
| 3 | A delivery is escalated | Give the team the conversation so far, and ask them to set the confirmation state by hand | The team's phone call is what decides what happens next, and the automation follows that state |
Field registry
Every field this automation reads or writes, by the name it carries in the system — what it means, and what changes when it changes. Field ids are in the developer reading.
Inputs — what it reads
Values the automation looks at to make its decision. Change one of these and the decision changes.
| Field | What it means | What it changes | Status |
|---|---|---|---|
| Customer Delivery ConfirmationSales Order | Where this delivery has got to in the confirmation conversation with the customer. | The single most important field here. Its value decides which saved search an order appears in, and therefore what the automation does next. Cleared to blank when two attempts go unanswered, which is what sends the order back for a new date. | confirmed |
| Scheduled Delivery DateSales Order | The date the customer is being asked to confirm, and the date we intend to deliver. | Supplies the date quoted in every message, and is part of the key that groups orders into one conversation. Overwritten when a customer picks a different offered date, and cleared on the no-confirmation reset. | confirmed |
| needs confirmingSales Order | The customer has authorised the driver to leave the delivery without anyone home. | Decides whether we ask for a reply at all. An ATL order whose whole group is Standard Courier is told the date rather than asked to confirm it. | needs confirming |
| needs confirmingSales Order | How many times this order's delivery date has been changed. | At three or more, the order stops being handled automatically and is escalated to the fulfilment team instead. | needs confirming |
Outputs — what it writes
Values the automation puts back. Everything downstream of this rule reads them, so a wrong value here travels.
| Field | What it means | What it changes | Status |
|---|---|---|---|
| Customer Delivery ConfirmationSales Order | Where this delivery has got to in the confirmation conversation with the customer. | The single most important field here. Its value decides which saved search an order appears in, and therefore what the automation does next. Cleared to blank when two attempts go unanswered, which is what sends the order back for a new date. | confirmed |
| Scheduled Delivery DateSales Order | The date the customer is being asked to confirm, and the date we intend to deliver. | Supplies the date quoted in every message, and is part of the key that groups orders into one conversation. Overwritten when a customer picks a different offered date, and cleared on the no-confirmation reset. | confirmed |
| Ship DateSales Order | The date the order is due to leave the warehouse. | Cleared on the no-confirmation reset so the order re-enters Shipping Automation module 2, and set from the chosen run when a customer picks an offered date. | confirmed |
| Courier ApprovalSales Order | Whether a courier has been approved for this order. | Set back to false on the no-confirmation reset. Without this the order would not be picked up again for courier selection. | needs confirming |
| Reason for Service ChangeSales Order | Why the delivery service on this order was changed. | Cleared on the no-confirmation reset so the order is re-assessed from scratch rather than carrying a stale reason. | needs confirming |
| needs confirmingSales Order | The truck run allocated to this delivery. | Written when a customer picks one of the offered dates, so the delivery is booked onto the specific run that had the capacity. | needs confirming |
| needs confirmingSales Order | The date the goods travel on the linehaul leg for the chosen run. | Written alongside the chosen date when the selected run uses a linehaul truck. | needs confirming |
Four Lambdas in ap-southeast-2, one DynamoDB table (life-delivery-date-comms) and one SMS long
code (+61400751852) shared across all of them. NetSuite is the source of truth for the delivery;
DynamoDB holds the conversation. The Customer Delivery Confirmation field (CDC) is the state
machine — its value is what moves an order between the saved searches, so a write to CDC is
effectively a queue transition.
Inputs
What it reads, and where from.
| Field / source | System | Type | Notes |
|---|---|---|---|
| Saved search 7249 | NetSuite | feed | Life truck orders, delivery date set, CDC empty — the initial ask |
| Saved search 7326 | NetSuite | feed | CDC = Pending – 1st Attempt — the follow-up |
| Saved search 7327 | NetSuite | feed | CDC = Pending – 2nd Attempt — the last chance before release |
| Saved search 7506 | NetSuite | feed | Confirmed deliveries due a reminder; the search does the date filtering, not the Lambda |
| Saved search 7250 | NetSuite | feed | Escalation feed — three or more date changes, or missing/landline phone |
custbody_delivery_confirmation |
NetSuite | select | Values 1–7; see the CDC value map below |
custbody_scheduled_delivery_date |
NetSuite | date | Quoted in messages, part of the grouping key |
custbodyauthority_to_leave |
NetSuite | checkbox | Drives the no-reply path in LI-BL-FUL-008 |
custbody_scheduled_delivery_date_chang |
NetSuite | integer | Reschedule counter behind LI-BL-FUL-017 |
| Inbound SMS | AWS End User Messaging → SNS | event | Linked to its outbound message by previousPublishedMessageId |
| Availability | lifeinte-fulfillment API |
JSON | Runs with capacity for the address and volume; see the availability chain below |
CDC value map. Ids are the NetSuite internal ids.
| Id | Label | Written by |
|---|---|---|
| 1 | Confirmed – Scheduled Date OK | Inbound, on a yes (LI-BL-FUL-011) and on an accepted date (LI-BL-FUL-013) |
| 2 | Pending – 1st Attempt | Outbound, after the first message |
| 3 | Pending – 2nd Attempt | Outbound, after the second message |
| 4 | Manual - Exclude from automation | Never written. Input only — it is how a person takes an order out of automation |
| 5 | Requested Specific Date | Never written by anything. See Known limitations |
| 6 | Required Life Interior team to reach out | Inbound, on every hand-off (LI-BL-FUL-014) |
| 7 | ATL – Scheduled Date OK | Outbound, on the Authority to Leave path (LI-BL-FUL-008) |
| — | (blank) | Outbound, on the no-confirmation reset (LI-BL-FUL-009) |
The availability chain. The board draws this as one Google Sheet. In production it is four hops, and the sheet is at the far end:
"WH PICKING SCHEDULE - INTEGRATION SCHEDULE" (Google Sheet)
+ NetSuite CBM / stop limits
-> ms-courier-schedule-loader-v2 (direct Lambda invoke)
-> courier-schedule-sync (~10 min EventBridge schedule)
-> Supabase: runs, run_availability_days, run_capacity_days
-> lifeinte-fulfillment API
-> inbound Lambda, when a customer says no
Processing
What it does with that, step by step.
OUTBOUND (scheduled, three phases per run, in this order)
for phase in [reschedule (7327), follow-up (7326), initial (7249)]:
fetch the phase's saved search
drop orders with no valid mobile
group orders by phone + scheduled delivery date + ATL flag
for each group, take the group's CDC state:
CDC empty and ATL and every order is standard courier
-> informational SMS ; CDC := 7
CDC empty -> ask to confirm ; CDC := 2
CDC = 1st attempt -> ask again ; CDC := 3
CDC = 2nd attempt -> tell them it will be rescheduled
and RESET (below)
anything else -> raise for review, touch nothing
process 25 groups, then re-invoke self for the next batch
(always from index 0 — NetSuite removes processed orders from the search)
RESET -> ship date := null
scheduled date := null
courier approval := false
reason for change:= null
CDC := null // all five, or it will not fall back
INBOUND (on each customer reply)
find the open conversation by the message it replies to
if the conversation is closed -> post-close path, NO NetSuite write
classify the reply:
reply to an options SMS?
a number we offered -> DATE SELECTION
anything else -> hand to team
"y" / "n" exactly -> yes / no
contains no|cancel|reschedule|change
and options already offered -> hand to team
otherwise -> no
contains yes|ok|confirm|confirmed|good -> yes
otherwise -> hand to team
yes -> CDC := 1 ; confirm by SMS ; close
no -> ask availability engine for up to 3 dates
got dates -> send numbered list ; CDC unchanged
got none -> CDC := 6
date selection -> CDC := 1 ; write chosen date, ship date,
truck, linehaul date ; confirm by SMS ; close
hand to team -> CDC := 6
Outputs & side effects
What it writes, and who reads it afterwards.
| Output | Written to | Downstream consumer |
|---|---|---|
custbody_delivery_confirmation |
NetSuite Sales Order | The three confirmation saved searches — this is the queue transition |
custbody_scheduled_delivery_date |
NetSuite Sales Order | Truck scheduling, the reminder search, customer messaging |
shipdate, custbody27, custbody_reasonforservicechange |
NetSuite Sales Order | Cleared together on reset so Shipping Automation module 2 picks the order up |
custbody_detrack_truck_number, custbody_scheduled_linehaul_date |
NetSuite Sales Order | DeTrack run allocation and linehaul planning |
| Conversation record | DynamoDB life-delivery-date-comms |
Inbound matching, the reminder's idempotency check, the audit trail on escalations |
| SMS | AWS End User Messaging | The customer |
| Note / conversation | Kustomer, queue Logistics & Fulfilment | Fulfilment team follow-up |
| Error email | SES | devs@, kim@, and on escalations fulfilmentandlog@ |
Worked examples
Real records carried end to end, so the logic can be checked against something that happened.
Example 1 — the ordinary case: two orders, one van, customer says yes
| Step | Value | Why |
|---|---|---|
| Input | Two Life truck orders, same phone, both dated Mon 7 Sep, ATL off, CDC empty | Same customer, same day, same terms |
| Grouping | One group phone + 2026-09-07 + false |
LI-BL-FUL-006 — one van, one message |
| Sent | "…scheduled for delivery on Mon 7 Sep. Please reply YES to confirm or NO if you need to reschedule." | First attempt |
| Written | CDC := 2 on both orders | Moves both out of 7249 and into 7326 |
| Reply | "Yes please" | Contains yes |
| Written | CDC := 1 on both orders; conversation closed | LI-BL-FUL-011 |
Example 2 — the awkward case: customer declines, picks an offered date
| Step | Value | Why |
|---|---|---|
| Input | One order, CDC = 2, dated Mon 7 Sep, 4.2 CBM to 2170 | Follow-up already sent |
| Reply | "no that week is no good" | Contains no → valid NO |
| Decision | Availability engine returns 3 runs with capacity | LI-BL-FUL-012 |
| Sent | "1. Mon, 14 Sep / 2. Mon, 21 Sep / 3. Mon, 28 Sep — Reply 1–3…" | Offer built from real runs |
| Written | Nothing on the order. Options stored against the conversation | CDC stays 2 — nothing is agreed yet |
| Reply | "2" | Matches an offered option |
| Written | Scheduled delivery date := 21 Sep, ship date, truck and linehaul date from run 2; CDC := 1 | LI-BL-FUL-013 |
Example 3 — nobody answers
| Step | Value | Why |
|---|---|---|
| Input | CDC = 3 (second attempt already sent), dated Mon 7 Sep | Last chance has passed |
| Sent | "…we haven't received your confirmation after two attempts… Your delivery will be rescheduled." | Customer is told, even though the date is going |
| Written | Ship date, scheduled delivery date, reason for service change all null; courier approval false; CDC null | LI-BL-FUL-009 — all five, or the order will not fall back |
| Result | Order reappears in Shipping Automation module 2 for a new date | The whole point of the reset |
Test cases
What to run to know the behaviour is intact. Expected values, never “should work”.
| Case ID | Layer | Preconditions | Input | Expected output | Rule ref |
|---|---|---|---|---|---|
DC-TC-T1 |
needs confirming | CDC empty, Life truck, date set | Outbound runs | Confirmation SMS sent, CDC := 2 | LI-BL-FUL-005 LI-BL-FUL-007 |
DC-TC-T2 |
needs confirming | CDC = "Manual - Exclude from automation" | Outbound runs | No SMS, no field written | LI-BL-FUL-005 |
DC-TC-T3 |
needs confirming | Three orders, same phone and date, ATL matching | Outbound runs | Exactly one SMS; CDC written on all three | LI-BL-FUL-006 |
DC-TC-T4 |
needs confirming | Two orders, same phone and date, ATL differs | Outbound runs | Two SMS, two conversations | LI-BL-FUL-006 |
DC-TC-T5 |
needs confirming | ATL on, all orders standard courier | Outbound runs | Informational SMS, CDC := 7, no reply expected | LI-BL-FUL-008 |
DC-TC-T6 |
needs confirming | ATL on, one order is two-man | Outbound runs | Normal confirm-or-decline SMS, CDC := 2 | LI-BL-FUL-008 |
DC-TC-T7 |
needs confirming | CDC = 3 | Outbound runs | Reschedule SMS; all five fields cleared | LI-BL-FUL-009 |
DC-TC-T8 |
needs confirming | Open conversation | Reply "Y" | CDC := 1 | LI-BL-FUL-010 LI-BL-FUL-011 |
DC-TC-T9 |
needs confirming | Open conversation | Reply "yes but can we make it earlier" | CDC := 1 — contains-matching wins | LI-BL-FUL-010 |
DC-TC-T10 |
needs confirming | Open conversation | Reply "no" | Up to 3 dates offered, CDC unchanged | LI-BL-FUL-012 |
DC-TC-T11 |
needs confirming | Options already offered | Reply "no" again | CDC := 6, no second list | LI-BL-FUL-010 LI-BL-FUL-014 |
DC-TC-T12 |
needs confirming | Options 1–3 offered | Reply "9" | CDC := 6, nothing booked | LI-BL-FUL-010 LI-BL-FUL-014 |
DC-TC-T13 |
needs confirming | Options 1–3 offered | Reply "2" | Date, ship date, truck, linehaul written; CDC := 1 | LI-BL-FUL-013 |
DC-TC-T14 |
needs confirming | Record has no CBM or address | Reply "no" | No options offered; CDC := 6 | LI-BL-FUL-012 |
DC-TC-T15 |
needs confirming | Closed conversation | Any reply | DynamoDB updated, Kustomer note raised, no NetSuite write | LI-BL-FUL-015 |
DC-TC-T16 |
needs confirming | Reminder already sent today | Reminder runs again | No second SMS | LI-BL-FUL-016 |
DC-TC-T17 |
needs confirming | Date changed 3 times | Escalation sweep runs | Kustomer conversation in Logistics & Fulfilment | LI-BL-FUL-017 |
UAT
What a person checks, by hand, before it is trusted.
| # | Step | What to check | Signed off by | Date |
|---|---|---|---|---|
| U1 | Find a Life truck order with a delivery date and no confirmation state | An SMS arrives quoting the right date, and the order's Customer Delivery Confirmation reads Pending – 1st Attempt | ||
| U2 | Reply YES from the customer's number | The field changes to Confirmed – Scheduled Date OK and a confirmation SMS arrives | ||
| U3 | On a second test order, reply NO | Up to three dates arrive, and the order's confirmation state is unchanged until you pick one | ||
| U4 | Reply with one of the offered numbers | The Scheduled Delivery Date moves to that date and the state reads Confirmed – Scheduled Date OK | ||
| U5 | Reply with something ambiguous, e.g. "maybe next week?" | The state reads Required Life Interior team to reach out, and nothing is booked | ||
| U6 | Leave a test order unanswered through both attempts | Ship Date, Scheduled Delivery Date, Reason for Service Change and Courier Approval are all cleared, and the order reappears for shipping automation | ||
| U7 | Reply to an order that is already confirmed | A note appears in the Logistics & Fulfilment queue and nothing changes in NetSuite |
Edge cases
The inputs that sit at the boundary, and whether each is handled.
| Case | Behaviour | Handled? |
|---|---|---|
| Several orders to one customer on one day | Grouped into one conversation and one SMS | ✅ |
| Same customer, same day, different Authority to Leave | Treated as separate conversations | ✅ |
| Customer replies "yes" to the numbered options list | Treated as an invalid selection and handed to the team, not as a confirmation | ✅ |
| Customer replies a number we never offered | Handed to the team; nothing booked | ✅ |
| Customer says no twice | Second no is handed to the team rather than re-offering the same dates | ✅ |
| Reply arrives after the conversation closed | Recorded, acknowledged, escalated; NetSuite untouched | ✅ |
| Conversation record predates the availability feature (no CBM/address) | No options offered; handed to the team quietly rather than erroring | ✅ |
| Landline or missing mobile | Never enters the SMS conversation; picked up by the escalation sweep | ✅ |
| Test numbers in the configured skip list | Ignored on the inbound side | ✅ |
| Customer replies to the reminder | Reminder is no-reply; the conversation is usually closed, so it lands in the post-close path | ✅ |
| Order's CDC holds a value the outbound Lambda does not recognise | Raised for review; nothing written | ✅ |
| Customer changes their mind after picking an offered date | The conversation is closed, so it becomes a post-close escalation for a person | ✅ |
Failure modes
What breaks it, how that shows, and how to recover.
| Failure | Symptom | Detection | Recovery |
|---|---|---|---|
| Availability engine unavailable | Customers who say no are handed to the team instead of being offered dates | Rise in Required Life Interior team to reach out; SES error email | Fix upstream; the team works the queue by phone in the meantime |
| Courier schedule stale in Supabase | Dates are offered that no longer have capacity | courier-schedule-sync errors; capacity conflicts at run planning |
Re-run the sync; it upserts, so a re-run is safe |
| SMS sent but NetSuite write fails | Customer told one thing, order says another | SES error email | Re-drive the order; the DynamoDB conversation holds what the customer was told |
| NetSuite write succeeds but confirmation SMS fails | Order is confirmed, customer never told | SES error email; the outcome is deliberately kept | Send manually — do not re-drive, the order is already correct |
| Reset writes only some of the five fields | Order silently never re-enters shipping automation | Order sits with no date and no queue | Clear the remaining fields by hand; all five are required |
| Outbound self-invocation loop | Repeated batches on the same orders | Safety limit stops the phase and raises an error | Investigate why NetSuite is not removing processed orders from the search |
| Duplicate reminder | Two reminders one day | Idempotency check on the conversation record | Check the Sydney-timezone day boundary |
Known limitations
Scope that was deliberately chosen. A limitation is a decision.
| Limitation | Why it is this way | What to do instead |
|---|---|---|
| Requested Specific Date (CDC value 5) is never written by anything | The flow was scoped with a "customer asked for a date" state, but the built behaviour confirms the chosen date outright instead. The value survives on the list, unused | Read Confirmed – Scheduled Date OK as covering both. Do not build reporting that expects value 5 |
| Only two confirmation attempts | A third message does not produce answers and holds capacity | The escalation sweep catches customers who repeatedly cannot be reached |
| Unreadable replies are treated as rejections, never as confirmations | A wrong yes puts a truck on the road; a wrong hand-off costs a phone call | Accepted cost — it produces more team hand-offs than a looser parser would |
| Only Standard Courier ATL groups skip the confirmation ask | A two-man or full-service delivery cannot be left unattended | Mixed groups are asked to confirm as normal |
| At most three dates are offered | A longer list is unreadable in an SMS | Customers who need something else are handed to the team |
| The reminder writes nothing to NetSuite | It is informational; the delivery is already agreed | Use the conversation record for the audit trail |
| Replies after close never update NetSuite | The automation cannot tell a confirmation from a cancellation at that point | The team decides, then sets the field by hand |
N_FIELD_NOCONF = '4' is dead config in the inbound and escalation repos |
Left over from an earlier design that had a "No Confirmation" state | Ignore it — and note that 4 is Manual - Exclude from automation, so acting on it would exclude the order from automation |
Known defects
Where it does something other than what was decided. A defect is a mistake.
Open questions
What could not be established. Recorded rather than guessed.
| Ref | Status | Question | Who can answer |
|---|---|---|---|
DC-Q1 |
Open | The outbound EventBridge schedule is not in any repository. The ~24 hour gap between attempts comes from how often the outbound Lambda runs, so the real cadence lives in the AWS console. | needs confirming |
DC-Q2 |
Open | S1.3, the reminder, has no CI deployment and no workflow run in its repository. Its behaviour is documented from code; whether that code is the code running is unconfirmed. Treat LI-BL-FUL-016 as the least verified rule here. |
needs confirming |
DC-Q3 |
Open | N_SEARCHID_FINAL = '7328' appears in the escalation repo's staging config and in no production config. It may be a fourth stage that was started and parked. |
needs confirming |
DC-Q4 |
Open | Saved search names are not recorded — only ids. The links resolve, but if a search is renamed the name will not help anyone find it. | needs confirming |
These are unresolved at the time of writing and are recorded rather than guessed:
Source references (read-only)
The code and searches this reading was written from.
lifeinteriors/lifedeldateconfirm-s11-outbound/controllers/app.mjs@85d4a74— phases, grouping, message selection, reset trigger (verified 2026-08-24)lifeinteriors/lifedeldateconfirm-s11-outbound/controllers/ns.mjs@85d4a74— the five-field reset (verified 2026-08-24)lifeinteriors/lifedeldateconfirm-s12-inbound/helpers/utils.mjs@e7e44ef— reply classification (verified 2026-08-24)lifeinteriors/lifedeldateconfirm-s12-inbound/controllers/responseHandlers.mjs@e7e44ef— yes / no / date-selection / hand-off outcomes (verified 2026-08-24)lifeinteriors/lifedeldateconfirm-s12-inbound/helpers/noResponseTemplate.mjs@e7e44ef— the three-date offer (verified 2026-08-24)lifeinteriors/lifedeldateconfirm-s13-reminder/controllers/app.mjs@7a55ec6— reminder and idempotency (verified 2026-08-24)lifeinteriors/lifedeldatefailed-s22-kustomer/controllers/app.mjs@1750369— escalation and note text (verified 2026-08-24)lifeinteriors/courier-schedule-sync/README.md@09b8de6— the availability chain behind the offered dates (verified 2026-08-24)
Change history
What moved on this page, and when.
| Date | Change | Change request |
|---|---|---|
| 2026-09-28 | Developer sections reordered to the registry's skeleton (CONVENTIONS §15): Inputs · Processing · Decision tree · Outputs · Worked examples · Test cases · UAT · Edge cases · Failure modes · Known limitations · Known defects · Open questions · Source references · Change history. The entries had drifted into two house styles — four put the proof after the outputs, four put the problems there — so every shared heading sat at a different position depending on which entry you opened; Test cases alone appeared at five different ones. Every section moved whole and byte-identical: nothing inside any of them was touched, and no wording changed. The one-line description the site now prints under each heading is generated from build.mjs, not written here, so it reads the same on every entry. npm run check warns on a wrong order and on a missing section. No logic change, and last_reviewed is unchanged. |
— |
| 2026-09-28 | 17 test cases moved from the body table into the test_cases: front-matter register and restated in the shape a test pack uses — Case ID · Layer · Preconditions · Input · Expected output · Rule ref. Ids become DC-TC-<case>, which keeps every case id the table already used while making them unique across the registry. The build renders them back under ### Test cases, and they now also appear on the generated Test cases page, so a pack spanning two entries no longer has to be retyped. 17 name no layer — neither is inferred, and npm run check now counts them. No expected value was changed. No behaviour was re-documented and last_reviewed is unchanged. |
— |
| 2026-09-28 | Defects and open questions moved from body tables into the defects: and open_questions: front-matter registers, namespaced DC-: 4 open questions (4 still open). The build renders them back under the same headings, so the page reads as it did; the prose around those tables is unchanged. They now also appear on the generated DEFECTS and Open questions pages, which is the point — the same list was unreadable spread across nine entries in five different column shapes. Every existing ref is preserved. No behaviour was re-documented, no defect status was reinterpreted, and last_reviewed is unchanged. |
— |
| 2026-09-28 | System slug dynamodb renamed aws-dynamodb, so every AWS service this entry touches carries the aws- prefix that aws-lambda and aws-end-user-messaging already used. systems: drives the blast-radius queries and the systems view of the Integration Map, where an unprefixed slug reads as a separate platform. npm run check now warns on a slug outside the known list. Front-matter only — no logic change, and last_reviewed is unchanged. |
— |
| 2026-08-31 | Placed on the order journey: journey_stage: pre-delivery (Pre-Delivery), the stage confirmed with the business. The Organisation Map now groups and orders entries by journey stage rather than by folder. Front-matter only — no logic change, and last_reviewed is unchanged. |
— |
| 2026-08-24 | Initial documentation of existing behaviour, from the four Lambda repositories and the scoping board. Divergences between the board and production resolved in favour of the code | — |
Field registry — ids
Inputs
Outputs
Hover any box and its explanation appears beside it. Click to pin it here.