# Email & text customers

These endpoints do what the **Email** and **Text** buttons on a job, contact or customer page do. The message goes out, and it's logged on the job and on the customer's timeline so the whole team sees it.

!!! warning
    `send-email` and `send-text` **reach a real customer**. Confirm the recipient and the wording before you call them, especially from an AI assistant. Nothing else on this page sends anything.

**Who it comes from.** Emails go from the API key creator's connected Gmail or Outlook mailbox, so replies land in their inbox. Without one, they go from the workspace's TruTec sender with replies going to the key creator, unless the workspace requires a connected mailbox (Settings › Email accounts). Texts go from the workspace's own texting number, which an owner sets up in TruTec.

**Merge fields.** `{{contact_first_name}}`, `{{contact_name}}`, `{{company_name}}`, `{{location_address}}`, `{{my_name}}` and `{{my_company}}` in a subject or message are filled in with the customer's details when it's sent. `GET /v1/message-templates` lists them with their values for a customer.

**Who can't be reached.** An email to an address on the workspace's unsubscribe list, or marked **do not contact** on any of its contacts, is refused with `403` `Do not contact`. A text is refused (`422` `Text not sent`, with the reason in `message`) when the number texted STOP, the contact is marked do not contact, it's outside 8am–9pm in the workspace's time zone, the workspace hit its daily texting limit, or the workspace has no texting number.

Writes need **Write jobs**; reads need **Read jobs**. Writes act as the user who created the key ([Authentication](authentication.md)); if that person has left the workspace, sends are refused with `403` `Key owner unavailable`.

## Email

### Email the customer about a job

`POST /v1/jobs/{id}/send-email`

**Permission:** Write jobs

| Field | Type | Required | Description |
|---|---|---|---|
| `to` | array or string | Yes | Up to 10 email addresses (a list, or one comma-separated string). |
| `cc` | array or string | No | Up to 10 addresses. |
| `subject` | string | Yes | Up to 255 characters. |
| `body` | string | Yes | The message as plain text (up to 20,000 characters). |
| `body_html` | string | No | A formatted version (paragraphs, bold, lists, links). When sent, the plain text is made from it. |
| `contact_id` | integer | No | The person it's logged against. Default: the job's primary contact. |
| `task_id` | integer | No | A follow-up on the job this email answers: the email becomes its notes. |
| `complete_task` | boolean | No | With `task_id`, also mark that follow-up done. |

```bash
curl -X POST "https://trutec.ai/api/v1/jobs/70412/send-email" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["pat@northgate.example"],
    "subject": "Sealcoat schedule for Northgate Plaza",
    "body": "Hi {{contact_first_name}},\n\nWe can start Monday the 20th. Does that work?\n\nThanks"
  }'
```

```json
{
  "data": {
    "sent": true,
    "to": ["pat@northgate.example"],
    "cc": [],
    "subject": "Sealcoat schedule for Northgate Plaza",
    "job_id": 70412,
    "contact_id": 5521,
    "client_account_id": 812,
    "from_email": "sam@yourpaving.example",
    "sent_with": "connected_mailbox",
    "job_activity_id": 991204,
    "contact_activity_id": 44120,
    "task": null
  }
}
```

`sent_with` is `connected_mailbox` (the user's own Gmail or Outlook) or `trutec` (the workspace's TruTec sender). When the mailbox refuses the email, the call returns `502` `Email not sent` with the mailbox's reason, and nothing is logged as sent.

### Email a contact

`POST /v1/contacts/{id}/send-email`

**Permission:** Write jobs

Same body as above, without `contact_id`, `task_id` and `complete_task`. Logged on the contact and their company.

### Email a customer company

`POST /v1/client-accounts/{id}/send-email`

**Permission:** Write jobs

Same body as the job email, with `contact_id` (optional, one of the company's people) and without the follow-up fields. Logged on the company.

## Texts

### Text the customer about a job

`POST /v1/jobs/{id}/send-text`

**Permission:** Write jobs

| Field | Type | Required | Description |
|---|---|---|---|
| `body` | string | Yes | Up to 1,600 characters. Merge fields are filled in. |
| `contact_id` | integer | No | Who to text. Default: the job's primary contact. |
| `phone` | string | No | Which of the contact's numbers (default: their primary mobile), or a number to text when the job has no contact. |
| `template_id` | string | No | The text template used, for reporting. |

```bash
curl -X POST "https://trutec.ai/api/v1/jobs/70412/send-text" \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"body": "Hi {{contact_first_name}}, crews arrive 7am Monday. Please keep the lot clear."}'
```

```json
{
  "data": {
    "sent": true,
    "conversation_id": 3310,
    "message_id": 44188,
    "to_phone": "+15125550143",
    "body": "Hi Pat, crews arrive 7am Monday. Please keep the lot clear.",
    "contact_id": 5521,
    "job_id": 70412,
    "sent_at": "2026-10-14T15:02:11Z"
  }
}
```

### Text a contact

`POST /v1/contacts/{id}/send-text`

**Permission:** Write jobs

Takes `body`, `phone`, `template_id`, and `job_id` to also log it on a job.

## Conversations

### List text conversations

`GET /v1/text-conversations`

**Permission:** Read jobs

One conversation per customer phone number, most recent first. Filter by `contact_id`, `client_account_id`, `phone` or `unread=true`; page with `limit` (default 50, maximum 200) and `offset`.

| Field | Type | Description |
|---|---|---|
| `id` | integer | |
| `phone` | string | E.164, for example `+15125550143`. |
| `contact`, `client_account` | object or null | `{id, name}`. |
| `unread` | boolean | A text came in that nobody has read in TruTec. |
| `opted_out` | boolean | They texted STOP. |
| `last_direction`, `last_message_preview`, `last_message_at`, `last_inbound_at` | | The latest text. |
| `owner_user_id` | integer or null | Whose conversation it is. |

### Get a conversation

`GET /v1/text-conversations/{id}`

**Permission:** Read jobs

The conversation with its texts in `messages`, oldest first: the newest 200, with `has_older: true` when there are more (pass `before_id`, the oldest message id you have, to page back). Each message has `id`, `direction` (`inbound` or `outbound`), `body`, `status`, `sent_by` (`{id, name}` for outbound texts), `job_id` and `created_at`. Reading a conversation doesn't mark it read in TruTec.

Emails sent and received on a job are on its timeline: `GET /v1/jobs/{id}/activity?type=email` ([Notes & tasks](notes-and-tasks.md)).

## Templates

### List message templates

`GET /v1/message-templates`

**Permission:** Read jobs

The workspace's saved CRM email templates (`kind=email`, the default) or text templates (`kind=text`), with `id`, `name`, `subject`, `body_text`, `body_html` and `is_default`, plus `merge_fields` (`token`, `label`, `value`). Pass `job_id`, `contact_id` or `client_account_id` to fill the merge fields with that customer's details. Templates are edited in TruTec (Settings › Email templates).
