gmail-channel-molecule (0.1.2)
Installation
pip install --index-url https://git.moleculesai.app/api/packages/molecule-ai/pypi/simple/ --extra-index-url https://pypi.org/simple gmail-channel-moleculeAbout this package
Gmail inbound channel bridge for a Molecule workspace (poll-mode A2A relay, stdlib-only, per-workspace OAuth).
gmail-channel-molecule
First-party Gmail inbound channel for Molecule workspaces. Polls a Gmail
mailbox (stdlib only) and relays each new message into a workspace over A2A
message/send, deduped on the Gmail Message-ID. Credentials are per-workspace
— minted via a Google device grant (gmail-connect skill) and stored as the
workspace's own secrets; nothing mailbox-specific is baked in.
Modeled on plugins/lark-channel. Discoverable + install-on-demand via the
molecule-core manifest.json plugin catalog (NOT auto-installed).
Layout
| Path | Purpose |
|---|---|
plugin.yaml |
Marketplace manifest — kind: channel, contributes the gmail-bridge daemon + gmail-connect skill. |
gmail_channel_molecule/bridge.py |
Pure decision helpers (plan_message/to_envelope) + Gmail REST + A2A relay + poll loop. |
gmail_channel_molecule/daemon.py |
Console entry — poll/daemon modes, env-wins credential loading, connect dispatch. |
gmail_channel_molecule/onboard.py |
Google device-grant onboarding → writes GMAIL_* workspace secrets + restart. |
skills/gmail-connect/SKILL.md |
Agent-driven connect flow (start → status → finalize). |
daemon-bootstrap.sh |
contributes.daemons entry — installs delivered source, execs the daemon. |
Connect
See skills/gmail-connect/SKILL.md. In short: create a Google OAuth client of
type "TV and Limited Input devices", export GMAIL_CLIENT_ID/GMAIL_CLIENT_SECRET,
then gmail-channel-molecule connect start | status | finalize.
Scope (v0.1.0)
- Inbound only (
gmail.readonly). Replies-to-email would be a v2 addinggmail.send. - The daemon runs from delivered source (no published wheel required). The
connectonboarding CLI ispip installed on demand; the wheel is published to the org Gitea PyPI registry byauto-release→publish(a green merge to main cuts tagv<version>, which builds + uploads the wheel), sopip install gmail-channel-molecule==0.1.0resolves. - Unit-tested (pure helpers + onboarding finalize contract); a live OAuth + relay verification against a real mailbox is recommended before fleet rollout.
Diagnosing a silent channel
The state dir (GMAIL_CHANNEL_STATE_DIR, else /workspace/.gmail-channel,
else ~/.gmail-channel) holds two files:
| File | Written | Read it to answer |
|---|---|---|
cursor.json |
only when mail moves | where the high-water-mark is; which messages are deferred |
status.json |
every tick, always | is the loop still ticking, and what did the last tick fail on |
cursor.json alone cannot distinguish "the daemon is wedged" from "the
mailbox is quiet" — both look like a frozen mtime. status.json carries
tick_finished_at and last_error, so a stale beat means wedged and a fresh
beat with a last_error names the failure.
Poll-loop invariants worth knowing before debugging:
- A tick cannot run forever:
GMAIL_TICK_BUDGET_SECONDS(default 300) is a hard SIGALRM deadline. urlopen'stimeout=is a per-socket-operation timeout, not a deadline, so it is not by itself a bound on a tick. - A single message can never wedge the channel: one that Gmail won't hand over is
skipped, one whose sender hasn't materialized is deferred (
MAX_DEFERticks, then promoted with a fallback sender), and one the platform refuses is retried next tick. In all three cases newer mail keeps flowing. - The cursor is a commit frontier: it never advances past a message that was
not relayed. Messages relayed ahead of the frontier are held in
_promoteduntil it catches up, which is what makes flowing past a stuck message safe from duplicates.