A gateway that connects any A2A (Agent-to-Agent) protocol agent to Telegram, Slack, WhatsApp, Google Chat, Email, Discord, and your own custom channels. You build the agent, the gateway handles the channels.
Telegram ──┐ ┌─────────────────────┐
Slack ──┤ │ Your A2A Agent │
WhatsApp ──┤ │ (ADK, LangGraph, │
G Chat ──┼──▶ a2a-gateway ──A2A─┤ CrewAI, custom…) │
Email ──┤ ◀──── /push (A2A) ────┤ │
Discord ──┤ └─────────────────────┘
Custom ──┘
Adding a built-in channel is mostly just setting env vars. Need a platform we don't support? Subclass SimpleChannel, implement one method, and you're in. The gateway takes care of message chunking, rate limiting, retries, debouncing, typing indicators, streaming responses, and per-channel markdown formatting. Everything is opt-in: if you don't configure a feature, it doesn't run.
docker run -p 8000:8000 \
-e A2A_SERVER_URL=http://host.docker.internal:8001 \
-e TELEGRAM_BOT_TOKEN=your-bot-token \
ghcr.io/eliasecchig/a2a-gateway:mainGet a bot token from @BotFather in under a minute.
Prerequisites: Python 3.12+ and uv.
git clone https://github.com/eliasecchig/a2a-gateway.git
cd a2a-gateway
uv syncRun with env vars (no config file needed):
export A2A_SERVER_URL=http://localhost:8001
export TELEGRAM_BOT_TOKEN=your-bot-token
uv run a2a-gatewayOr with a config file:
cp config.example.yaml config.yaml
uv run a2a-gateway --config config.yamlOr with the built-in test agent (a minimal A2A echo server — no LLM, no credentials needed):
uv run a2a-gateway --with-agentThe gateway starts on http://localhost:8000.
By default the gateway is single-user: one bot, one A2A agent, zero infra beyond the TELEGRAM_BOT_TOKEN env var. The simple case above stays simple.
For multitenant deployments — one bot on any channel (Telegram, Slack, WhatsApp, Discord, Google Chat, Email, custom) serving N users, each linked to a distinct A2A user identity, with proactive notifications via /push targeting a2a_user_id instead of raw chat IDs — opt in with MULTITENANT_MODE=true and set GATEWAY_ADMIN_TOKEN. Users enroll via https://t.me/...?start=<token> on Telegram or by typing connect <token> in DM on any other channel. State lives in SQLite by default (zero infra) or any asyncpg-compatible Postgres in production. See docs/multitenancy.md.
| Channel | Transport | Public URL needed? | Editing | Typing |
|---|---|---|---|---|
| Telegram | Bot API (polling) | No | Yes | Native |
| Slack | Socket Mode | No | Yes | Opt-in |
| Discord | Gateway (websocket) | No | Yes | Native |
| Meta Cloud API webhook | Yes | No | Opt-in | |
| Google Chat | Webhook + REST API | Yes | Yes | Opt-in |
| SMTP (aiosmtpd) | No | No | - |
All channels use official APIs.
Every inbound message goes through this pipeline. Each step is optional and controlled by config.
Inbound message
│
├─ ACK reaction (emoji / read receipt)
├─ Debounce (coalesce rapid messages)
├─ Group policy check (open / mention-only / disabled)
├─ Rate limit (A2A side)
├─ Concurrency limit
├─ Typing indicator
│
├─ ──▶ A2A agent ──▶ response (or SSE stream)
│
├─ Markdown adaptation
├─ Chunking (split long responses, preserve code fences)
├─ Rate limit (channel side)
│
└─ Send reply (or stream edits in real time)
| Topic | Description |
|---|---|
| Configuration | Env vars, config file, A2A auth, default/opt-in features |
| Custom channels | Build your own channel adapter with SimpleChannel |
| Push API | Send messages to any channel via the A2A JSON-RPC endpoint at POST /push |
A prebuilt image is published to GHCR on every push to main (see Quick start for usage). To build locally:
docker build -t a2a-gateway .
docker run -p 8000:8000 \
-e A2A_SERVER_URL=http://your-agent:8001 \
-e TELEGRAM_BOT_TOKEN=your-bot-token \
a2a-gatewayMount a config file: -v $(pwd)/config.yaml:/app/config.yaml.
uv run pytest tests/ -v # all offline
uv run pytest -m live tests/live/ -v # live integration testsSee CONTRIBUTING.md.
Copyright 2026 Google LLC. Licensed under the Apache License, Version 2.0. See LICENSE.