Launch-Free 3 months Builder plan-
Pixel art lobster working at a computer terminal with email — google adk agent email calendar tool tutorial

how to build a google adk agent with email and calendar tools

A practical tutorial for building a Google ADK agent that manages calendar events and sends email, with a simpler path for inbox provisioning.

8 min read
Ian Bussières
Ian BussièresCTO & Co-founder

Google's Agent Development Kit (ADK) is one of the more interesting frameworks to land in 2026. It gives you a Python-native way to build agents that call tools, coordinate sub-agents, and plug into Google's ecosystem. If you've been wanting an AI agent that can check your calendar, schedule meetings, and fire off emails without you touching a keyboard, ADK is a solid starting point.

But the email side of that equation gets messy fast. The Calendar API is straightforward: authenticate, read events, create events. Email through Gmail's API? That's where you hit the OAuth problem that makes Gmail integration painful for agents. Scopes, token refresh, consent screens, and the fundamental issue that Gmail accounts are designed for humans, not autonomous software.

I spent a week building a Google ADK agent that handles both calendar and email. Here's what worked, what didn't, and a shortcut that saved me from OAuth purgatory.

How to build a Google ADK agent with email and calendar tools#

If you want the quick overview before we get into details, here's the full process:

  1. Install ADK and dependencies with pip install google-adk google-auth google-api-python-client
  2. Enable the Gmail and Calendar APIs in your Google Cloud Console project
  3. Configure OAuth2 credentials and download credentials.json
  4. Define Python tool functions that wrap Calendar and Gmail API calls
  5. Register tools with the ADK Agent class and configure the model
  6. Test locally with adk web to verify tool calls work
  7. Deploy to Cloud Run or Vertex AI Agent Engine for production use

Each of those steps hides varying amounts of complexity. Steps 2 and 3 are where most people lose an afternoon. Let's walk through them.

Setting up your ADK environment#

ADK runs on Python 3.10+. Start with a virtual environment and install the core packages:

pip install google-adk google-auth google-api-python-client google-auth-oauthlib

You'll also need a Google Cloud project with the Calendar API and Gmail API enabled. Head to the Cloud Console, create a project (or use an existing one), navigate to "APIs & Services," and enable both.

For OAuth2 credentials, create an "OAuth consent screen" and then generate a "Desktop app" credential. Download the resulting credentials.json to your project root. This file is what your agent uses to request access tokens.

Here's the authentication helper I used:

from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import os, pickle

SCOPES = [
    "https://www.googleapis.com/auth/calendar",
    "https://www.googleapis.com/auth/gmail.send",
    "https://www.googleapis.com/auth/gmail.readonly",
]

def get_google_creds():
    creds = None
    if os.path.exists("token.pickle"):
        with open("token.pickle", "rb") as f:
            creds = pickle.load(f)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES)
            creds = flow.run_local_server(port=0)
        with open("token.pickle", "wb") as f:
            pickle.dump(creds, f)
    return creds

That SCOPES list is important. calendar gives full read/write access to events. gmail.send lets the agent compose and send. gmail.readonly lets it read incoming messages. You can narrow these if your agent only needs a subset, and Google recommends you do.

The catch: run_local_server() opens a browser window for a human to approve access. That works during development. In production, when your agent is running on a server with no browser, you need a different flow (service accounts, or pre-authorized refresh tokens). This is the first sign that Gmail wasn't built for agents.

Building the calendar tool#

The calendar tool is the easy win. Google Calendar's API is clean and well-documented:

from googleapiclient.discovery import build
from datetime import datetime, timedelta

def list_upcoming_events(n: int = 5) -> list[dict]:
    """Return the next n calendar events."""
    creds = get_google_creds()
    service = build("calendar", "v3", credentials=creds)
    now = datetime.utcnow().isoformat() + "Z"
    result = service.events().list(
        calendarId="primary",
        timeMin=now,
        maxResults=n,
        singleEvents=True,
        orderBy="startTime",
    ).execute()
    return [
        {"summary": e.get("summary", "No title"), "start": e["start"].get("dateTime", e["start"].get("date"))}
        for e in result.get("items", [])
    ]

def create_event(summary: str, start_iso: str, duration_minutes: int = 60) -> dict:
    """Create a calendar event and return its details."""
    creds = get_google_creds()
    service = build("calendar", "v3", credentials=creds)
    start = datetime.fromisoformat(start_iso)
    end = start + timedelta(minutes=duration_minutes)
    event = service.events().insert(
        calendarId="primary",
        body={
            "summary": summary,
            "start": {"dateTime": start.isoformat(), "timeZone": "UTC"},
            "end": {"dateTime": end.isoformat(), "timeZone": "UTC"},
        },
    ).execute()
    return {"id": event["id"], "link": event.get("htmlLink")}

Register these with your ADK agent and it can check what's on the calendar, create new events, and answer questions like "Am I free at 3pm tomorrow?" ADK handles the function-calling plumbing: it inspects your type hints and docstrings to generate the tool schema automatically.

Error handling matters here. The Calendar API can throw HttpError for quota limits, invalid time ranges, or permission issues. Wrap your calls in try/except and return a clear error message so the agent can tell the user what went wrong instead of crashing silently.

The email problem (and two ways to solve it)#

Now the hard part. Building a Gmail send tool looks simple on the surface:

import base64
from email.mime.text import MIMEText

def send_email(to: str, subject: str, body: str) -> dict:
    """Send an email via Gmail API."""
    creds = get_google_creds()
    service = build("gmail", "v1", credentials=creds)
    message = MIMEText(body)
    message["to"] = to
    message["subject"] = subject
    raw = base64.urlsafe_b64encode(message.as_bytes()).decode()
    sent = service.users().messages().send(
        userId="me", body={"raw": raw}
    ).execute()
    return {"id": sent["id"], "threadId": sent["threadId"]}

This works. In development. On your personal Gmail account. With your OAuth tokens cached locally.

In production, the problems stack up. Gmail's API has a send limit of 2,000 messages per day for Workspace accounts and 500 for free accounts. Your OAuth tokens expire and need refreshing. If the agent runs headless, there's no browser to re-authenticate. And the fundamental issue: the agent is borrowing a human's Gmail identity. Every email it sends comes from you@gmail.com, not from the agent itself.

If you're building an agent that schedules meetings over email, that might be fine. It's acting on your behalf, using your calendar, sending from your address.

But if you want the agent to have its own email identity, its own inbox, its own address that isn't tied to any human's Google account, the Gmail API path doesn't work. You'd need to create a Google Workspace account for the agent ($7.20/mo minimum), configure it, set up the OAuth flow for that account, and maintain the whole thing.

There's a simpler approach. Give the agent a dedicated inbox through LobsterMail, where provisioning takes one API call instead of an afternoon in the Google Cloud Console:

import { LobsterMail } from '@lobsterkit/lobstermail';

const lm = await LobsterMail.create();
const inbox = await lm.createSmartInbox({ name: 'Calendar Agent' });
// inbox.address → calendar-agent@lobstermail.ai

No OAuth. No token refresh. No human account required. The agent owns its inbox from the moment it's created.

If you want your ADK agent to have its own email, and your agent handles the rest.

Wiring it all together with ADK's agent class#

Here's where ADK shines. You define your agent, register the tools, and let the framework handle routing:

from google.adk.agents import Agent

agent = Agent(
    model="gemini-2.5-flash",
    name="calendar_email_agent",
    instruction="""You are a scheduling assistant. You can check the calendar,
    create events, and send email confirmations. When asked to schedule a
    meeting, check availability first, then create the event, then email
    the attendees.""",
    tools=[list_upcoming_events, create_event, send_email],
)

ADK's multi-agent pattern lets you split responsibilities further. You could have a calendar sub-agent and an email sub-agent, each with their own tools and instructions, coordinated by a root agent. For a simple assistant this is overkill. For a production system handling dozens of calendars and email accounts, it keeps things organized.

Test locally with adk web, which launches a browser UI where you can chat with the agent and watch tool calls execute in real time. This is genuinely useful for debugging; you can see exactly which tool the model chose, what arguments it passed, and what came back.

What I'd do differently#

After building this, here's my honest take. The Calendar integration is clean and works well through ADK. Google clearly designed the Calendar API for programmatic access.

The Gmail integration is functional but fragile. Token management alone adds enough complexity that I'd recommend it only when you specifically need to send from a human's existing Gmail address. For anything where the agent needs its own identity, skip the OAuth maze entirely.

If I were starting over, I'd use ADK for calendar and LobsterMail for email. Two tools, two different providers, each doing what they're good at. The agent doesn't care where its email comes from. It just calls the tool.

Frequently asked questions

What Python packages do I need for a Google ADK agent with email and calendar tools?

You need google-adk, google-auth, google-api-python-client, and google-auth-oauthlib. Install them all with pip install google-adk google-auth google-api-python-client google-auth-oauthlib.

How do I register a Google Calendar tool with an ADK agent?

Define a Python function with type hints and a docstring, then pass it to the tools parameter of the Agent class. ADK inspects the function signature to auto-generate the tool schema. No manual JSON schema needed.

What OAuth2 scopes are required for an ADK agent to read and send Gmail messages?

You need https://www.googleapis.com/auth/gmail.send for sending and https://www.googleapis.com/auth/gmail.readonly for reading. Add https://www.googleapis.com/auth/gmail.modify if the agent needs to mark messages as read or move them.

How do I store and refresh OAuth tokens securely when an ADK agent runs in the cloud?

Store refresh tokens in a secret manager (Google Secret Manager, AWS Secrets Manager, or similar). On each request, load the token, check if it's expired, and call creds.refresh(Request()) before making API calls. Never store tokens in plaintext files in production.

Can a Google ADK agent have its own email address instead of using a human's Gmail?

Not through the Gmail API alone. Gmail accounts require a human identity. You can either create a dedicated Google Workspace account for the agent or use an agent-native email service like LobsterMail where the agent provisions its own inbox with a single API call.

How do I trigger an ADK agent workflow when a new email arrives?

The Gmail API supports push notifications via Google Cloud Pub/Sub. Call users.watch() to register a topic, then subscribe to that topic and invoke your agent when a notification fires. Alternatively, LobsterMail supports webhooks that POST to your agent's endpoint on every inbound email.

What is the ADK multi-agent pattern and when should I use sub-agents?

ADK lets a root agent delegate to specialized sub-agents, each with their own tools and instructions. Use it when your agent handles distinct domains (calendar, email, CRM) and you want cleaner separation of concerns. For simple assistants, a single agent with multiple tools works fine.

How do I test ADK tool calls locally before deploying?

Run adk web from your project directory. It launches a local browser UI where you can chat with the agent, see tool call arguments, and inspect responses in real time.

What are the Gmail API rate limits for an ADK agent sending email?

Free Gmail accounts can send 500 messages per day. Google Workspace accounts get 2,000 per day. Per-second rate limits also apply (roughly 250 quota units per second). Exceeding these returns a 429 error and your agent must back off.

How does Google ADK compare to OpenAI function calling for email and calendar agents?

ADK has tighter integration with Google's APIs and supports multi-agent orchestration natively. OpenAI function calling is model-agnostic but requires you to wire up tool execution yourself. For Google Calendar specifically, ADK is the shorter path.

What is Google Agent Development Kit used for?

ADK is a Python framework for building AI agents that can call external tools, access Google services, and coordinate multiple sub-agents. Common use cases include scheduling assistants, email automation, data lookup, and multi-step workflows that span several APIs.

What's the easiest way to give an ADK agent a dedicated production email inbox?

Use LobsterMail. Your agent calls LobsterMail.create() and createSmartInbox() to get a working email address in seconds. No Google Cloud project, no OAuth consent screen, no Workspace subscription required.

How do I deploy an ADK email-calendar agent to run on a schedule?

Deploy to Google Cloud Run with a Cloud Scheduler trigger, or use Vertex AI Agent Engine for a managed option. Make sure your OAuth tokens are stored in Secret Manager and your agent handles token refresh on each invocation.

Related posts