Connecting your database
Connecting your database
Connect PostgreSQL, MySQL, SQL Server or MongoDB once, then use it three ways: fill a campaign from it, write call results back into it, and let an agent look records up mid-call.
All four appear under a single Database entry in the pickers — click it to choose your engine.
Before you start
You need three things from whoever runs the database.
| A host reachable from the internet | A private or VPC-internal address is refused. Ask for our egress IP to be allowlisted. |
| A read-only role | For reading, create a user with SELECT only, scoped to the tables you want. We enforce read-only on our side too, but a read-only role is what makes it true rather than promised. |
| A separate role if you want us to write | Writing back is opt-in and only touches the columns you map. Give that role INSERT/UPDATE on just those tables. |
Step 1 — Connect
Workplace → Integrations → Available → Database, then pick your engine.
| Field | |
|---|---|
| Host | db.example.com. Must be publicly reachable. |
| Port | Defaults to your engine's standard port if left blank. |
| Database | The database name. On MongoDB, the database — the collection is chosen later, per use. |
| User / Password | The role above. The password is encrypted before it is stored and never shown again. |
| TLS mode | verify-full (default), verify-ca, or require. Enter just the value — not sslmode=require. |
We test the connection when you save it. If the host is unreachable, the password is wrong, TLS fails or the database name is a typo, you are told immediately, with your database's own error message. A connection that saves green really did connect.
TLS mode is not just encryption.
verify-fullchecks the certificate and the hostname, which is what stops a hostname being re-pointed at another machine between our safety check and the connection. Lowering it weakens more than privacy.disableis refused outright.
If you enter a TLS mode we don't recognise, the connection still works — it runs at verify-full, the strictest — and you get a warning saying so, because a field that doesn't do what it says is worse than one that fails.
Step 2 — Choose what to do with it
| Use | Where | What it does |
|---|---|---|
| Fill a campaign | Campaign → Add from a source | Reads rows with a SELECT you write, and dials them |
| Log every call | Agent → Post-call actions | Writes a row per call into a table you choose |
| Look records up mid-call | Agent → Tools | The agent finds, creates or updates a record while talking |
Fill a campaign
Campaign → Add from a source → Database → your engine.
1. Write the query
A single read-only SELECT. Include an ORDER BY on a stable key — pages are read by offset, so without one your database may return rows in a different order between pages and some get skipped.
SELECT id, mobile, first_name, plan
FROM leads
WHERE called_at IS NULL
ORDER BY id
2. Press Read columns
This runs your query and shows you the columns and a few real rows. If the query is wrong you get your database's own message — column "phon" does not exist — not a generic failure.
3. Pick the phone column
Then choose Country for bare numbers. This defaults to your campaign's timezone, and only matters for numbers written without a country code.
| Your data | What we dial |
|---|---|
9876543210 | +919876543210 (India) |
098765 43211 | +919876543211 — one national trunk 0 is dropped |
+44 20 7946 0958 | unchanged — a number that already carries a country code is never re-guessed |
You don't need to clean your data first. Rows whose phone column holds something that isn't a number (N/A, an order id) are skipped and counted separately from empty ones, because junk there usually means the wrong column was picked.
4. Map columns to your agent's variables
If your agent's prompt says {{name}}, it needs a variable called name — a column called first_name won't fill it on its own. So each variable your agent declares gets a row:
name * ← first_name
plan ← Plan
renewal * ← — not filled —
Columns whose names already match are filled in for you. A required variable left unmapped is flagged before you import — a prompt with a hole in it is worth catching before 500 calls, not after.
Anything else you tick under Also send under their own names travels as-is.
5. Write results back (optional)
Choose what happens after each call:
- Update the row it dialled — stamps the lead. Keyed on the source's own key column, which is the only thing that reliably identifies the row the call came from.
- Insert a new row — appends to a table you name. A call log usually doesn't belong in the lead list.
Then map columns the same way a call destination does, with the same field picker.
Mid-call tools
Give an agent one of these under Agent → Tools → Add tool → Database, and it can use your database during a conversation.
| Tool | |
|---|---|
| Look up a record | "Let me pull up your booking" — finds by phone, reference, anything you allow |
| Create a record | Logs a ticket, a lead, a callback request |
| Update a record | Changes a record it looked up earlier in the same call |
Configuring a lookup
| Setting | |
|---|---|
| Table | The table (or MongoDB collection) to look in |
| Columns the agent may read | Tick them. Only these come back. |
| Columns it may search by | Usually the phone number and a reference |
The read list is the important one. Whatever comes back goes into the model's context and can be said out loud. A
customersrow often carries an email, a card suffix, internal notes — and columns added to that table next year would arrive too. So it is an explicit list, never "everything". The agent cannot search a column you didn't tick either, so it can't probe the table.
Configuring create and update
Pick the table, press Load fields, and tick the columns you want the agent to be able to fill. Those become the tool's named fields — the agent never guesses a column name, and a wrong choice is caught here rather than as a failed insert mid-call.
Update also needs an Identified by column — a primary key. The agent can only change the record whose key value the lookup returned, and only one record at a time. Include that column in the lookup's readable columns so the agent has the value.
If an update matches nothing, the agent is told it changed nothing rather than confirming a change that never happened.
What we will and won't do
- Reading is read-only by construction. Every statement is checked before it runs and the connection itself is opened read-only. Pulling contacts cannot alter your data.
- Writing is a separate opt-in and only touches the columns you map.
- We never
SELECT *. A mid-call lookup returns exactly the columns you ticked. - Credentials are encrypted with AES-256-GCM before they are stored; the key lives in the server environment, never in the database.
- We can't reach a private network. A host that isn't publicly routable is refused rather than attempted.
When something goes wrong
Errors carry your database's own words, wherever they surface — on the connection, on the campaign source, on the destination.
| You see | Usually means |
|---|---|
password authentication failed for user "x" | Wrong credentials |
no pg_hba.conf entry for host | Our egress IP isn't allowlisted |
relation "leads" does not exist | Wrong table, or the role can't see it |
column "outcome" of relation "calls" does not exist | A mapped column name is wrong |
N record(s) rejected | Rows were refused — the reason follows |
| is not a publicly routable address | The host is private or internal |
A campaign that dials nobody, or a table that stops filling, will have its reason recorded on the source or destination — not only in a log you can't read.