Your setup
voice AI guide
Step 6 of 7
Step 06 · The unlock

Email and calendar.

Give your agent access to Gmail and Google Calendar via direct OAuth. Read your inbox. Send emails. Check your availability. Add events. About thirty minutes.

This is the killer-feature unlock. Once your agent can see your calendar, it stops being a phone tool and starts being an actual assistant.

We will walk through this in three parts: set up a Google Cloud project to enable the APIs, get a one-time OAuth refresh token using Google's own playground tool, then add four agent tools that read and write Gmail and Calendar. No code generation needed for the OAuth dance, no third-party connectors.

For Outlook users, the same pattern works with Microsoft Graph. We focus on Google here because the OAuth Playground makes the token capture click-only.

Part A: Enable the APIs

  1. Open Chrome. Go to console.cloud.google.com. Sign in with your Google account.
  2. At the top of the page, click the project picker (it says "Select a project" or shows a current project name).
  3. Click New Project. Name it voice-bot. Click Create. Wait for the spinner to finish.
  4. Make sure the project picker now shows voice-bot. If not, click the picker and select it.
  5. In the search bar at the top, type Gmail API. Click the result.
  6. Click the blue Enable button. Wait a few seconds.
  7. Search for Google Calendar API. Click the result. Click Enable.

Part B: Create OAuth credentials

OAuth is what lets your agent act as you on Gmail and Calendar without you handing over your password. Google needs to know your project, what scopes (permissions) you want, and where to send the user after consenting.

Configure the consent screen

  1. In the Cloud Console left sidebar, click APIs & Services, then OAuth consent screen.
  2. Pick External as the user type. Click Create.
  3. App name: voice-bot. User support email: your email. Developer contact: your email. Click Save and continue.
  4. Skip Scopes for now. Click Save and continue.
  5. On Test users, click Add users, type your own Gmail address, click Add, then Save and continue.
  6. Click Back to Dashboard.

Create the OAuth client

  1. In the left sidebar, click Credentials.
  2. Click Create credentials at the top, then pick OAuth client ID.
  3. Application type: Web application.
  4. Name: voice-bot-client.
  5. Under Authorized redirect URIs, click Add URI. Paste exactly: https://developers.google.com/oauthplayground
  6. Click Create.
  7. A modal appears with your Client ID and Client Secret. Copy both. Save them to your notes file.

You now have an OAuth client. Next, get the refresh token, which is the long-lived key your Worker will use forever.

Part C: Get a refresh token via OAuth Playground

Google ships a tool called OAuth Playground that handles the auth dance for you. No code needed.

  1. Open Chrome. Go to developers.google.com/oauthplayground.
  2. Click the gear icon in the top right.
  3. Tick Use your own OAuth credentials.
  4. Paste your OAuth Client ID and OAuth Client secret from Part B. Close the gear panel.
  5. In the left list, scroll to find and tick all four of these scopes:
    https://www.googleapis.com/auth/gmail.modify
    https://www.googleapis.com/auth/gmail.send
    https://www.googleapis.com/auth/calendar.events
    https://www.googleapis.com/auth/calendar.readonly
  6. Click the blue Authorize APIs button at the bottom of step 1.
  7. You will be redirected to Google's consent screen. Pick your account. Click Continue through the warning ("Google hasn't verified this app", click Advanced → Go to voice-bot). Grant all four permissions.
  8. You will be redirected back to the playground with a code in step 2.
  9. Click Exchange authorization code for tokens.
  10. You will see a Refresh token and an Access token in the response. Copy the Refresh token. This is the one you keep.

The refresh token does not expire. This is the only key you will ever need, as long as you do not revoke it from your Google account settings.

Part D: Add the secrets to your Worker

Three new values to store: client ID, client secret, refresh token.

  1. Open Chrome. Go to dash.cloudflare.com, then your voice-bot Worker.
  2. Click Settings, then Variables and Secrets.
  3. Add three new Secret variables: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN. Paste the matching value from your notes for each. Save each.
wrangler secret put GOOGLE_CLIENT_ID
wrangler secret put GOOGLE_CLIENT_SECRET
wrangler secret put GOOGLE_REFRESH_TOKEN

Part E: Add the tools to your Worker

Replace your Worker's code with this version. It adds four new endpoints (one per tool) plus a helper that exchanges the refresh token for a short-lived access token whenever the agent calls one of the tools.

const TOKEN_URL = "https://oauth2.googleapis.com/token";
const GMAIL_API = "https://gmail.googleapis.com/gmail/v1/users/me";
const CAL_API = "https://www.googleapis.com/calendar/v3";

async function getAccessToken(env) {
  const r = await fetch(TOKEN_URL, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      client_id: env.GOOGLE_CLIENT_ID,
      client_secret: env.GOOGLE_CLIENT_SECRET,
      refresh_token: env.GOOGLE_REFRESH_TOKEN,
      grant_type: "refresh_token",
    }),
  });
  const data = await r.json();
  return data.access_token;
}

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // Tool: read recent emails
    if (url.pathname === "/tools/read_email" && request.method === "POST") {
      const token = await getAccessToken(env);
      const r = await fetch(`${GMAIL_API}/messages?maxResults=10&q=is:unread`, {
        headers: { Authorization: `Bearer ${token}` },
      });
      const data = await r.json();
      return Response.json({ messages: data.messages || [] });
    }

    // Tool: send email
    if (url.pathname === "/tools/send_email" && request.method === "POST") {
      const body = await request.json();
      const token = await getAccessToken(env);
      const raw = btoa(
        `To: ${body.to}\r\nSubject: ${body.subject}\r\n\r\n${body.text}`
      ).replace(/\+/g, "-").replace(/\//g, "_");
      const r = await fetch(`${GMAIL_API}/messages/send`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ raw }),
      });
      return Response.json(await r.json());
    }

    // Tool: read calendar
    if (url.pathname === "/tools/read_calendar" && request.method === "POST") {
      const token = await getAccessToken(env);
      const now = new Date().toISOString();
      const r = await fetch(
        `${CAL_API}/calendars/primary/events?timeMin=${now}&maxResults=10&singleEvents=true&orderBy=startTime`,
        { headers: { Authorization: `Bearer ${token}` } }
      );
      return Response.json(await r.json());
    }

    // Tool: create calendar event
    if (url.pathname === "/tools/create_event" && request.method === "POST") {
      const body = await request.json();
      const token = await getAccessToken(env);
      const r = await fetch(`${CAL_API}/calendars/primary/events`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          summary: body.title,
          start: { dateTime: body.start },
          end: { dateTime: body.end },
        }),
      });
      return Response.json(await r.json());
    }

    // Existing WhatsApp handler from step 4 + 5 stays here
    // (paste your previous default handler below this line)
    // ...
  },
};

Important: the existing WhatsApp handler from step 4 and step 5 needs to stay in this file. The cleanest way is to merge: keep your old code's fetch body intact, and add the four new if (url.pathname === ...) blocks above it. The existing match for "call +1xxx about ..." stays as the fallback at the end. Save and deploy.

Part F: Register the tools in ElevenLabs

Tell your EL agent these tools exist so it knows when to call them.

  1. Open Chrome. Go to elevenlabs.io, then your agent.
  2. Scroll to the Tools section on the agent page.
  3. Click Add tool.
  4. Pick Webhook. Name: read_email. Description: "Reads the user's most recent unread emails and returns the senders and subjects." URL: https://voice-bot.yourname.workers.dev/tools/read_email (replace yourname). Method: POST. Save.
  5. Click Add tool again. Name: send_email. Description: "Sends an email on behalf of the user. Confirm with the user before sending." URL: /tools/send_email. Define parameters: to (string), subject (string), text (string).
  6. Add read_calendar: "Returns the user's next ten calendar events." URL: /tools/read_calendar.
  7. Add create_event: "Creates a new calendar event." URL: /tools/create_event. Parameters: title (string), start (ISO datetime), end (ISO datetime).
  8. Save the agent.

Test it

In the EL test panel (top right of the agent page), type chat messages or use the test call.

If those work, your agent is now a real assistant. Take a moment. The hard parts are done.