# Run a small CRM in Airtable that texts

> Keep your clients in an Airtable table, text any of them from your own number by ticking a box or changing their status, and get their replies, their stops and each text’s state written back to the row.

Nina runs Suds on Wheels, a mobile dog-grooming van, and keeps her clients in one Airtable table: the owner, the dog, the phone number and when the next groom is due. When a groom is due she copies the number into her phone and types the same text forty times a month. With this guide, she ticks **Send text** on a row (or moves a client to **Reminder due**) and the text goes from her own number. The client’s reply, a STOP, and whether the text went all come back to the same row.

## What you’ll have

- A **Send text** box on each row. Tick it and the row’s text goes from your number within seconds.
- The same automation on a status change, so moving a client to **Reminder due** texts them.
- Each reply written to the row with its time, the row’s **Status** set to **Replied**, and **Stopped** when someone texts STOP.
- Each text’s state (`sent`, `delivered`, `failed`) on the row, so you can see who never got theirs.

## What you need

- A BlueBot account with your Mac connected.
- An Airtable base you can add fields and automations to, on a plan that includes the **Run a script** automation action.
- A Make account for writing replies back. Make’s free plan is enough for a small table.

## How it fits together

Airtable texts through a BlueBot **Text Hook**: an address at BlueBot that turns each post it gets into a text. An Airtable automation posts the row to the hook, so no BlueBot API key sits in your base. Replies and states come back through BlueBot’s Make app, which hears about them the moment they happen and updates the row.

The row’s Airtable record ID travels with the text as [metadata](https://usebluebot.com/docs/concepts/metadata). BlueBot echoes it on every update about that text, so the state always finds its row, however the phone number is written.

### Set up the table

Make a table called **Clients** (or add these to yours). Names matter, because the steps below refer to them.

| Field            | Type                       | What it holds                                                                                  |
| ---------------- | -------------------------- | ---------------------------------------------------------------------------------------------- |
| Name             | Single line text (primary) | “Bella (Rosa Diaz)”                                                                            |
| First name       | Single line text           | “Rosa”                                                                                         |
| Mobile           | Phone number               | Any format: `(555) 555-0123` is fine.                                                          |
| Mobile digits    | Formula                    | `RIGHT(REGEX_REPLACE({Mobile}, "[^0-9]", ""), 10)`, the last ten digits, for matching replies. |
| Status           | Single select              | New, Reminder due, Texted, Replied, Booked, Stopped                                            |
| Text to send     | Long text                  | The words for this row’s next text.                                                            |
| Send text        | Checkbox                   | Tick to send. The automation unticks it.                                                       |
| Last text status | Single line text           | Filled in by Make.                                                                             |
| Message ID       | Single line text           | Filled in by Make, `msg_…`.                                                                    |
| Last reply       | Long text                  | Filled in by Make.                                                                             |
| Last reply at    | Date, with time            | Filled in by Make.                                                                             |

Text only clients who agreed to hear from you by text. A column such as **OK to text** (checkbox) that the automation checks is a good habit.

### Make the Text Hook

In the BlueBot dashboard, open **Text Hooks**, press **New hook**, name it “Airtable clients” and leave the preset empty. Copy the hook’s address, `https://usebluebot.com/h/` followed by a long token. Anyone with it can make your hook text somebody, so keep it as you would a password.

Set the mapping (the next step sends a sample post so you can pick fields, or type the paths now):

| Text Hook setting | Set it to                                                               |
| ----------------- | ----------------------------------------------------------------------- |
| Recipient         | `{{body.phone}}`                                                        |
| Text              | `{{body.text}}`                                                         |
| Metadata          | `record_id` set to `{{body.record_id}}`, and `source` set to `airtable` |
| Create a contact  | On, with the tag `airtable`                                             |

The text is the row’s own **Text to send**, so each client can get different words. Save the hook.

### Add the automation that sends

In Airtable, open **Automations** and press **Create automation**. Name it “Text the client”.

1. **Trigger:** **When a record matches conditions**, table **Clients**, conditions: **Send text** is checked, **Mobile** is not empty, and **Status** is not **Stopped**.
2. **Action:** **Run a script**. Add four input variables from the trigger’s record: `recordId` (the record’s ID), `phone` (**Mobile**), `firstName` (**First name**) and `text` (**Text to send**). Paste this script and put your hook’s address in `HOOK_URL`:

```js title="Run a script"
// Posts this row to BlueBot's Text Hook, which texts it from your own number.
const HOOK_URL = 'https://usebluebot.com/h/YOUR_HOOK_TOKEN'; // anyone who can edit automations can read this

const { recordId, phone, firstName, text } = input.config();
if (!phone || !text) {
  throw new Error('This row has no Mobile or no Text to send.');
}

const response = await fetch(HOOK_URL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ record_id: recordId, phone, first_name: firstName, text }),
});

if (!response.ok) {
  throw new Error(`BlueBot answered ${response.status}. Open the hook in BlueBot to see why.`);
}
```

3. **Action:** **Update record**, table **Clients**, record ID from the trigger. Set **Send text** to unticked and **Status** to **Texted**.

Unticking **Send text** takes the row out of the condition, so ticking it again later sends again. A failed run leaves the box ticked, which tells you which rows didn’t go.

To text on a status change instead, make a second automation with the condition **Status** is **Reminder due** and the same two actions.

Turn the automation on.

### Write replies, stops and states back with Make

In Make, make a scenario called “BlueBot replies to Airtable”:

1. BlueBot’s **Watch Incoming Texts** module, connected with a BlueBot API key that has `messages:read` and `webhooks:manage` (the module makes its own BlueBot webhook).
2. Airtable’s **Search Records** on **Clients**, with this formula, putting BlueBot’s **From** where the quotes are: `{Mobile digits} = RIGHT("From", 10)`. Set **Limit** to 1.
3. Airtable’s **Update a Record** with the **Record ID** from the search. Set **Last reply** to BlueBot’s **Text**, **Last reply at** to **Received At**, and **Status** to **Replied**.

Make a second scenario, “BlueBot states to Airtable”:

1. BlueBot’s **Watch Text Status Changes** module.
2. A filter that passes only texts whose **Metadata** has `record_id`, so texts from elsewhere are left alone.
3. Airtable’s **Update a Record** with **Record ID** set to **Metadata: record_id**. Set **Last text status** to **State** and **Message ID** to **ID**.

And a third, “BlueBot stops to Airtable”:

1. BlueBot’s **Watch Contact Stops** module.
2. Airtable’s **Search Records** with `{Mobile digits} = RIGHT("Handle", 10)`, putting the contact’s **Handle** where the quotes are.
3. Airtable’s **Update a Record**, setting **Status** to **Stopped**.

Turn all three scenarios on. They run when BlueBot tells them something happened, not on a timer.

### Test it with your own number

Add a row for yourself: your own first name, your own mobile number, and a short **Text to send** (“Test from Airtable”). Tick **Send text**.

| You got                                                                    | It means                                                                    | Do next                                                                       |
| -------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| A text on your phone, **Send text** unticked and **Status** at **Texted**  | The sending half works.                                                     | Wait a few seconds for **Last text status**.                                  |
| **Last text status** shows `delivered` or `sent`                           | The state half works. SMS stops at `sent`; iMessage goes on to `delivered`. | Reply from a second phone of your own to test replies.                        |
| **Send text** still ticked and the automation’s run history shows an error | The script couldn’t post, or BlueBot refused the post.                      | Read the error in the run history. A `404` means the hook’s address is wrong. |
| The text is in **Messages** in the dashboard with a `hold`                 | It’s accepted and waiting, often for quiet hours at night.                  | It goes on its own when the hold ends. [Holds](https://usebluebot.com/docs/concepts/holds)          |
| Your reply came but the row didn’t change                                  | The search found no row, often because **Mobile digits** isn’t ten digits.  | Check the formula field on your row.                                          |

## When it goes wrong

- **Ticking twenty boxes sends twenty texts slowly.** That’s pacing: your Mac sends one text every 20 seconds by default, the way a person would. They all go; the last one about seven minutes after the first. [Sending rules](https://usebluebot.com/docs/concepts/sending-rules)
- **A reply from somebody not in the table.** The search finds nothing and Make stops that run, which is what you want. Their text is still in BlueBot’s inbox and on your phone.
- **Two rows share a number** (two dogs, one owner). The reply goes to whichever row the search finds first. Give each owner one row and list the dogs in it, or tell Make which to pick by a second condition.
- **You changed the hook’s address.** Update `HOOK_URL` in the script. The old address stops working as soon as you make a new one.

## Next

- [Airtable recipes](https://usebluebot.com/docs/recipes/airtable): Text when a record enters a view, save replies, and keep delivery state on the record from your own script.
- [Text Hooks](https://usebluebot.com/docs/text-hooks): Templates, metadata, presets and the test panel.
- [Google Sheets](https://usebluebot.com/docs/guides/google-sheets): The same idea with a spreadsheet and Zapier.
- [Make](https://usebluebot.com/docs/make): Every BlueBot module in Make and what it outputs.
