You will create a Cloudflare D1 database (free tier: 5 million reads, 100,000 writes per day, far more than personal use), connect it to your Worker, and update the Worker code to log every call.
Create the database
- Open Chrome. Go to dash.cloudflare.com. Sign in.
- In the left sidebar, click Workers & Pages, then D1.
- Click Create database.
- Name it
voice-bot-memory. Pick a location close to you. Click Create.
Add the table
- On the new database page, click the Console tab.
- Paste this SQL into the console:
CREATE TABLE calls (
id TEXT PRIMARY KEY,
to_number TEXT,
objective TEXT,
summary TEXT,
created_at INTEGER
);
- Click Execute. You should see "Success" or similar.
Bind the database to your Worker
- In the left sidebar, click Workers & Pages, then click your
voice-botWorker. - Click Settings, then Bindings.
- Click Add, then pick D1 database.
- Variable name:
DB. Database: pickvoice-bot-memory. - Click Save. The Worker will redeploy automatically.
From your voice-bot project directory:
wrangler d1 create voice-bot-memory
Wrangler outputs a database ID. Add it to wrangler.toml at the bottom:
[[d1_databases]]
binding = "DB"
database_name = "voice-bot-memory"
database_id = "<the-id-wrangler-printed>"
Create the table:
wrangler d1 execute voice-bot-memory --command \
"CREATE TABLE calls (id TEXT PRIMARY KEY, to_number TEXT, objective TEXT, summary TEXT, created_at INTEGER);"
Re-deploy so the binding takes effect:
wrangler deploy
Wire memory into the Worker
The Worker now has a database connection. We need to make it actually use the database. Two changes: log each call when it starts, and look up prior calls to the same number to give your agent context.
Open your Worker code (browser editor or src/index.js) and replace it with the version below.
export default {
async fetch(request, env) {
if (request.method !== "POST") return new Response("OK");
const form = await request.formData();
const message = (form.get("Body") || "").toString();
const from = (form.get("From") || "").toString();
if (!message || !from) return twiml("");
const match = message.match(/call\s+(\+\d+)\s+about\s+(.+)/i);
if (!match) return twiml("Format: call +1xxx about <objective>");
const [, toNumber, objective] = match;
// Look up prior context for this number
let context = "";
if (env.DB) {
const prior = await env.DB.prepare(
"SELECT summary FROM calls WHERE to_number = ? ORDER BY created_at DESC LIMIT 1"
).bind(toNumber).first();
if (prior && prior.summary) {
context = "Prior call note: " + prior.summary;
}
}
// Trigger the EL outbound call
const callRes = await fetch(
"https://api.elevenlabs.io/v1/convai/twilio/outbound-call",
{
method: "POST",
headers: {
"xi-api-key": env.ELEVENLABS_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
agent_id: env.ELEVENLABS_AGENT_ID,
agent_phone_number_id: env.ELEVENLABS_AGENT_PHONE_ID,
to_number: toNumber,
conversation_initiation_client_data: {
dynamic_variables: {
objective,
context,
your_name: env.YOUR_NAME,
agent_name: env.AGENT_NAME,
},
},
}),
}
);
const data = await callRes.json();
// Log the call as a row
if (env.DB && data.conversation_id) {
await env.DB.prepare(
"INSERT INTO calls (id, to_number, objective, summary, created_at) VALUES (?, ?, ?, ?, ?)"
).bind(data.conversation_id, toNumber, objective, "", Date.now()).run();
}
return twiml(`${env.AGENT_NAME} is calling ${toNumber} now.`);
},
};
function twiml(reply) {
const body = `<Response>${reply ? `<Message>${escape(reply)}</Message>` : ""}</Response>`;
return new Response(body, { headers: { "Content-Type": "text/xml" } });
}
function escape(s) {
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
}
Save and re-deploy. (Browser route: click Save and Deploy in the editor. Terminal route: wrangler deploy.)
Test it
Send the same WhatsApp message you sent in step 4. The Worker now logs the call. After it finishes, you can confirm the row was written.
- In Chrome, go to dash.cloudflare.com, then Workers & Pages, then D1, then voice-bot-memory.
- Click Console. Run:
SELECT * FROM calls; - You should see one row with your test call's data.
The next time your agent calls the same number, it will get the prior summary in its context. Once the post-call summary feature is wired (step 7), this loop closes fully.