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 internetA private or VPC-internal address is refused. Ask for our egress IP to be allowlisted.
A read-only roleFor 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 writeWriting 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
Hostdb.example.com. Must be publicly reachable.
PortDefaults to your engine's standard port if left blank.
DatabaseThe database name. On MongoDB, the database — the collection is chosen later, per use.
User / PasswordThe role above. The password is encrypted before it is stored and never shown again.
TLS modeverify-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-full checks 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. disable is 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

UseWhereWhat it does
Fill a campaignCampaign → Add from a sourceReads rows with a SELECT you write, and dials them
Log every callAgent → Post-call actionsWrites a row per call into a table you choose
Look records up mid-callAgent → ToolsThe 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 dataWhat we dial
9876543210+919876543210 (India)
098765 43211+919876543211 — one national trunk 0 is dropped
+44 20 7946 0958unchanged — 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 recordLogs a ticket, a lead, a callback request
Update a recordChanges a record it looked up earlier in the same call

Configuring a lookup

Setting
TableThe table (or MongoDB collection) to look in
Columns the agent may readTick them. Only these come back.
Columns it may search byUsually 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 customers row 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 seeUsually means
password authentication failed for user "x"Wrong credentials
no pg_hba.conf entry for hostOur egress IP isn't allowlisted
relation "leads" does not existWrong table, or the role can't see it
column "outcome" of relation "calls" does not existA mapped column name is wrong
N record(s) rejectedRows were refused — the reason follows
is not a publicly routable addressThe 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.