Customer notices & dispatch
Tell the customer when their work is scheduled, record their approval of a date, send the dispatch packet to crews, and manage the crew's packet link.
View as MarkdownOnce a visit is on the schedule, two groups of people need to hear about it: the customer (when the crew is coming) and the crew (where to go and what to do). These endpoints do both, the same way the visit drawer and the Dispatch board do in TruTec.
Warning
notify-customer and retry-customer-text email or text your customer. send-dispatch and schedule/send-dispatch text and email your crew. None of these can be unsent. Show the user who it goes to before you call them.
Who the API acts as. Writes are made as the person who created the API key, with their own TruTec permissions. Customer emails go out from that person's connected mailbox (the one they connected in TruTec), so replies come back to them. Customer texts go out from the workspace's own texting number, only between 8am and 9pm workspace time. If the key's creator has left or switched workspaces, writes return 403 Key owner unavailable. Partner keys act as the workspace owner.
Operations must be on. These endpoints are part of TruTec's operations tools. If they are not turned on for the workspace, they return 403 Operations not enabled.
Holds don't notify anyone. A "hold a date" block (see Holds & schedule requests) is never sent to a customer or a crew; these endpoints return 400 for a hold.
Customer schedule notice#
Preview the notice#
GET /v1/work-orders/{id}/customer-notice
Permission: Read jobs
What Email customer would send for this visit, and who it can go to. Sends nothing.
{
"data": {
"work_order_id": 4120,
"job_name": "Northgate Plaza reseal",
"work_type": "Sealcoating",
"scheduled_for": "Tuesday, November 3, 2026",
"subject": "Schedule update: Northgate Plaza reseal — Tuesday, November 3, 2026",
"body": "Northgate Plaza reseal has been scheduled for Tuesday, November 3, 2026.\nWork: Sealcoating\n\nPlease reply to this email if you have any questions or site-access updates.",
"email_contacts": [
{ "name": "Property Manager", "email": "[email protected]", "role": "Primary", "primary": true }
],
"cc_options": [
{ "name": "Sales Rep", "email": "[email protected]", "role": "Salesperson" }
],
"text_contacts": [
{ "name": "Property Manager", "phone": "+15125550100", "role": "Job contact", "primary": true }
],
"texting_ready": true,
"texting_open_now": true,
"confirmation": { "requested_at": null, "confirmed_at": null, "note": null },
"customer_notified_at": null,
"history": [
{ "sent_at": "2026-10-20T15:02:11Z", "status": "sent", "to": ["[email protected]"], "cc": [], "opened_at": "2026-10-20T16:40:00Z", "open_count": 2 }
]
}
}
| Field | Description |
|---|---|
email_contacts |
The job's contacts with an email address, primary first. |
text_contacts |
The job's customer phones. Texts can only go to these numbers. |
texting_ready |
The workspace has its own texting number. Without it, texts fail; email instead. |
texting_open_now |
It's between 8am and 9pm in the workspace's time zone. |
confirmation |
When you last asked the customer to confirm the date, and when they did. |
history |
The last ten schedule emails for this visit, with when they were opened. |
Email or text the customer#
POST /v1/work-orders/{id}/notify-customer
Permission: Write jobs
Warning
This emails and/or texts your customer, and cannot be unsent.
| Field | Type | Required | Description |
|---|---|---|---|
to |
array of strings | One of to or text_to |
Customer email addresses. |
cc |
array of strings | No | Copies, for example the salesperson from cc_options. Up to 30 addresses in all. |
text_to |
array of strings | One of to or text_to |
Up to 5 phones from text_contacts. Any other number is refused (400). |
subject |
string | No | Defaults to the standard subject from the preview. |
body |
string | No | The email message. Defaults to the standard message. Texts always use the short standard text. |
request_confirmation |
boolean | No | Adds a one-click Confirm this date button to the email (and the link to texts). The customer needs no account. |
curl -X POST "https://trutec.ai/api/v1/work-orders/4120/notify-customer" \
-H "X-API-Key: sk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "to": ["[email protected]"], "text_to": ["+15125550100"], "request_confirmation": true }'
{
"data": { "id": 4120, "status": "scheduled", "scheduled_date": "2026-11-03", "customer_notified_at": "2026-10-28T16:02:11Z", "...": "the full work order" },
"emailed_to": ["[email protected]"],
"cc": [],
"texts": [{ "to": "+15125550100", "sent": true, "error": null }],
"confirmation_requested": true
}
The email is logged on the job's timeline and its opens are tracked. A text outside texting hours comes back with "error": "quiet_hours" and is not sent. If the email itself can't be sent (no connected mailbox, for example), or only texts were asked for and none went out, the response is 502.
Record the customer's approval#
POST /v1/work-orders/{id}/record-customer-approval
Permission: Write jobs
When the customer said yes to the date by phone, by email or in person, record it so the visit shows the date as approved. The approval, who gave it and how are written to the job's history. Sends nothing.
| Field | Type | Required | Description |
|---|---|---|---|
customer_name |
string | Yes | Who approved, up to 120 characters. |
method |
string | Yes | phone, email or in_person. |
note |
string | No | Up to 300 characters. |
scheduled_date |
string | No | The date they approved. Defaults to the visit's current date; if the visit has moved since, the response is 409. |
Returns the work order and already_recorded (true when the date was already approved, by the customer's own click or an earlier record; nothing changes then).
Retry a failed schedule text#
When a schedule text to the customer failed before it went out (texting not set up yet, quiet hours, a bad number), the office can send it again after checking the number. It's two steps, so the person sees the exact number and message first.
GET /v1/work-orders/{id}/customer-text-retry
Permission: Read jobs
Returns the phone, contact_name, the exact message, can_retry (with a reason when it can't go now) and a retry_token valid for 5 minutes. When there is no failed text to retry, or its outcome is uncertain, or the visit changed since, the response is 409 and says why.
POST /v1/work-orders/{id}/retry-customer-text with {"retry_token": "..."}
Permission: Write jobs
Warning
This texts your customer.
Sends the text once. outcome is accepted, failed (not sent; check texting setup), not_sent (the contact or visit changed; nothing was sent) or unknown (check its sending status before trying again).
Dispatch to crews#
Dispatching sends the crew everything they need for a visit: an in-app alert to each crew member with TruTec access that opens the visit in the crew app, plus a text and an email with a link to the dispatch packet (site, scope, map, safety sheets and job documents) for members with a phone or email on file. It is separate from publishing the schedule: publishing tells crews what's on their calendar; dispatching sends the packet for a specific visit.
Send a visit's dispatch#
POST /v1/work-orders/{id}/send-dispatch
Permission: Write jobs
Like Send dispatch in TruTec. The visit needs a crew with members. Assignment rules (for example a required certification) are checked first; a rule that isn't met returns 409 with violations.
{
"data": { "id": 4120, "dispatched_at": "2026-11-02T22:10:00Z", "...": "the full work order" },
"crew_notified_count": 3,
"texts_sent": 3,
"texts_failed": 0,
"emails_sent": 2,
"missing_contacts": ["Laborer 2 (no app access)"],
"packet_url": "https://trutec.ai/dispatch/packet/Xy3…",
"jsa": { "required": true, "complete": false, "missing": ["JSA acknowledgement"] }
}
missing_contacts lists people the packet could not reach. jsa is the visit's safety-sheet status (job safety analysis): what the crew still has to complete.
Send a day's dispatch#
POST /v1/schedule/send-dispatch
Permission: Write jobs
Like Send to crews: dispatches every scheduled or in-progress visit with a crew on one day.
| Field | Type | Required | Description |
|---|---|---|---|
scheduled_date |
string | Yes | The day, YYYY-MM-DD. |
crew_ids |
array of integers | No | Only these crews. Default: every crew with work that day. |
Returns totals (work_order_count, sent_count, skipped_count, texts_sent, texts_failed, emails_sent, missing_contacts) and one results row per visit with sent and, for a skipped visit, error.
Get the crew's packet link#
POST /v1/work-orders/{id}/dispatch-link
Permission: Write jobs
The secure link to the visit's dispatch packet, to share however you like. Anyone with the link can open the packet without signing in until expires_at. Returns the current link when there is one, otherwise makes a new one. Sends nothing.
{
"data": {
"work_order_id": 4120,
"url": "https://trutec.ai/dispatch/packet/Xy3…",
"expires_at": "2026-12-02T22:10:00Z",
"crew_map_url": "https://trutec.ai/dispatch/packet/Xy3…/crew-map",
"schedule_published": true,
"crew": { "id": 18, "name": "Seal Crew 1", "members": [{ "name": "Crew Lead", "role": "lead" }] },
"recipients": [
{ "user_id": 344, "name": "Crew Lead", "role": "lead", "has_app_access": true, "has_phone": true, "has_email": true }
],
"jsa": { "required": false, "complete": true, "missing": [] }
}
}
Turn the packet link off#
DELETE /v1/work-orders/{id}/dispatch-link
Permission: Write jobs and Delete records
Every current packet link for the visit stops working (for example after it was shared with the wrong person). Returns 204. The next dispatch, or POST .../dispatch-link, makes a new link.