Skip to main content
Glama
Gmail MCP Gateway

Gmail MCP Gateway

by systheno

Gmail MCP 网关

一个 MCP 服务器,让 AI 代理能够完整读取和管理多个 Gmail 账户——并且无法发送、丢弃或删除邮件。

                     Gmail MCP Gateway

        ALLOWED                        FORBIDDEN
        ───────                        ─────────
        Search                         Send
        Read messages                  Send draft
        Read threads                   Trash
        Read attachments               Delete
        Create drafts                  Mark spam
        Edit drafts                    Gmail settings
        Archive                        Forwarding rules
        Read / unread                  Arbitrary API calls
        Labels

该保证通过应用程序代码强制执行,而非通过向客户端下达指令实现。即使 MCP 客户端存在缺陷、被攻破或遭受提示注入,也无法通过此网关发送电子邮件,因为没有任何代码路径允许这样做。


目录


Related MCP server: Gmail MCP Server

快速开始

需要 Python 3.11+。五步,大约十分钟,大部分时间在 Google 控制台内。

1. 安装

git clone <this-repo> gmail-mcp-gateway
cd gmail-mcp-gateway
uv sync                                  # or: python -m venv .venv && .venv/bin/pip install -e .
.venv/bin/gmail-mcp-gateway --version

可选:将其放入 PATH 中,以便下面的示例更自然:

export PATH="$PWD/.venv/bin:$PATH"

2. 创建 Google OAuth 客户端

一次性操作,免费,并且你之后添加的每个账户都可以共享。

  1. 在 https://console.cloud.google.com/ 创建一个项目。

  2. APIs & Services → Library → 启用 Gmail API。

  3. APIs & Services → OAuth consent screen → External,填写必填字段,在 Test users 下添加你自己的 Google 账户。

  4. 发布应用(如果你仍然是唯一用户,则无需验证审核)。跳过此步骤会使应用保持在“测试”状态,Google 会在 7 天 后过期刷新令牌,你将需要每周重新授权。

  5. Credentials → Create credentials → OAuth client ID → Desktop app → 下载 JSON。

这里不需要选择作用域。网关在授权时仅请求所需权限,并拒绝请求 Gmail 之外的任何内容。

3. 安装 OAuth 客户端

install -Dm600 ~/Downloads/client_secret_*.json \
  ~/.local/share/gmail-mcp-gateway/secrets/oauth_client.json

这是你唯一需要手动放置的文件。加密密钥会在首次运行时自动生成。

4. 授权账户

gmail-mcp-gateway accounts add personal

浏览器会打开;批准请求的权限,勾选 所有 复选框(如果权限被拒绝,网关会大声报错而不是部分工作)。刷新令牌会加密存储,之后网关可以无人值守运行。

可以添加任意数量的账户——每个账户都有自己的授权、刷新令牌、加密密钥、速率限制桶和审计轨迹:

gmail-mcp-gateway accounts add work
gmail-mcp-gateway accounts add newsletters --read-only   # Google itself refuses writes

5. 验证

gmail-mcp-gateway health          # exit 0 = ready, 2 = something is wrong
[ok  ] directories      config=/home/you/.config/gmail-mcp-gateway ...
[ok  ] database         /home/you/.local/share/gmail-mcp-gateway/gateway.db
[ok  ] master_key       loaded
[ok  ] oauth_client     configured
[ok  ] accounts         1/1 authorized

gmail-mcp-gateway 1.0.0: healthy

然后,将你的 MCP 客户端指向它——参见 连接 MCP 客户端——或者先进行测试:

uv run python scripts/try-it.py --account personal

环境变量

对于普通的本地安装,你不需要设置任何环境变量。 上面的快速开始没有设置任何环境变量。默认配置位于 ~/.config,数据和密钥位于 ~/.local/share,加密密钥会自动生成。

这些变量适用于容器、systemd 单元和密钥管理器等场景——在这些地方,磁盘文件不是合适的机制。

变量

必需?

默认值

用途

GMAIL_MCP_OAUTH_CLIENT_ID

否¹

—

Google OAuth 客户端 ID

GMAIL_MCP_OAUTH_CLIENT_SECRET

否¹

—

Google OAuth 客户端密钥

GMAIL_MCP_MASTER_KEY

否²

自动生成

base64 编码的 32 字节凭据加密密钥

GMAIL_MCP_CONFIG_DIR

否

~/.config/gmail-mcp-gateway

config.toml;不包含机密信息

GMAIL_MCP_DATA_DIR

否

~/.local/share/gmail-mcp-gateway

gateway.db,attachments/

GMAIL_MCP_SECRETS_DIR

否

<data>/secrets

密钥、OAuth 客户端、凭据

GMAIL_MCP_HTTP_HOST

否

127.0.0.1

HTTP 绑定地址

GMAIL_MCP_HTTP_PORT

否

8765

HTTP 绑定端口

GMAIL_MCP_HTTP_ENABLED

否

false

通过配置启用 HTTP 传输

GMAIL_MCP_ALLOW_REMOTE_BIND

否

false

允许非回环绑定

GMAIL_MCP_LOG_LEVEL

否

INFO

DEBUG…CRITICAL

¹ 替代 secrets/oauth_client.json。提供文件 或 这对变量。 ² 未设置时,网关会在首次运行时创建 secrets/master.key(模式 0600)。

环境变量覆盖 config.toml,而 config.toml 覆盖默认值。

生成每个值

OAuth 客户端 ID 和密钥 —— 来自快速开始步骤 2 中下载的 JSON。如果要用环境变量而不是文件:

jq -r '.installed.client_id'     ~/Downloads/client_secret_*.json
jq -r '.installed.client_secret' ~/Downloads/client_secret_*.json

主密钥 —— 32 个随机字节,base64 编码:

openssl rand -base64 32
# or, without openssl:
python3 -c "import base64,secrets; print(base64.b64encode(secrets.token_bytes(32)).decode())"

此密钥用于解密你存储的刷新令牌。如果在添加账户后更改密钥,凭据将变得不可读,每个账户都需要执行 accounts reauth。请将密钥备份到数据目录的备份位置。

网关承载令牌 —— 不是环境变量。这是 MCP 客户端通过 HTTP 传输发送的内容,与任何 Google 凭据无关。CLI 生成它,并仅存储 SHA-256 哈希:

gmail-mcp-gateway token create my-agent

明文令牌只打印一次,并放入 客户端 的配置中。

使用 env 文件

网关不会自动读取 .env —— 安全工具不应悄无声息地从启动目录吸收密钥。复制 .env.example,其中记录了每个变量,并显式加载:

cp .env.example .env       # already covered by .gitignore
$EDITOR .env
set -a && source .env && set +a
gmail-mcp-gateway health

systemd 使用 EnvironmentFile=;Docker Compose 使用 env_file:。


运行方式

stdio —— 通常的选择

客户端将网关作为子进程启动,并通过管道进行通信。没有端口,没有令牌,没有网络暴露。Google 凭据始终保留在网关进程内;客户端仅看到工具调用。

gmail-mcp-gateway serve --transport stdio

手动运行时,它看起来会挂起——这是正常的,因为它正在等待 stdin 上的 JSON-RPC。通常你的 MCP 客户端会为你启动它。

可流式 HTTP —— 独立服务

适用于长期运行的服务,或无法生成进程的客户端。

gmail-mcp-gateway token create my-agent          # once; save the printed token
gmail-mcp-gateway serve --transport http --host 127.0.0.1 --port 8765

端点绑定到回环地址,需要承载令牌,并启用了 DNS 重新绑定保护。GET /healthz 无需认证,仅报告存活状态。

绑定非回环地址需要 GMAIL_MCP_ALLOW_REMOTE_BIND=true,即便如此,互联网可路由地址也会被拒绝。对于远程客户端,请使用隧道:

ssh -L 8765:127.0.0.1:8765 gateway-host

systemd

deploy/gmail-mcp-gateway.service 以专用系统用户身份运行,在强化沙箱中——ProtectSystem=strict,空的 CapabilityBoundingSet,seccomp 过滤器,NoExecPaths 覆盖数据目录。安装步骤位于单元文件的头部。在启动之前,以服务用户身份以交互方式一次性授权账户。

Docker

deploy/Dockerfile 和 deploy/docker-compose.yml 以非 root 和只读方式运行,放弃所有能力,仅将端口发布到回环地址。镜像中不包含凭据;它们位于 /secrets 卷中。

docker compose -f deploy/docker-compose.yml up -d

一次性授权序列——放置 OAuth 客户端、运行授权流程(发布重定向端口)、生成令牌——在 compose 文件的头部注释中有说明。


连接 MCP 客户端

stdio

{
  "mcpServers": {
    "gmail": {
      "command": "/absolute/path/to/gmail-mcp-gateway/.venv/bin/gmail-mcp-gateway",
      "args": ["serve", "--transport", "stdio"]
    }
  }
}

Claude Code:

claude mcp add gmail -- /absolute/path/to/.venv/bin/gmail-mcp-gateway serve --transport stdio

HTTP

{
  "mcpServers": {
    "gmail": {
      "type": "http",
      "url": "http://127.0.0.1:8765/mcp",
      "headers": { "Authorization": "Bearer <token from `token create`>" }
    }
  }
}

客户端永远不会挂载或以其他方式访问 Google 凭据文件。在 stdio 模式下,客户端与管道通信;在 HTTP 模式下,它持有一个与任何 Google 凭据无关的网关令牌。


工具参考

每个工具都接受一个 account 别名——没有默认账户。可变操作工具可接受一个可选的 client_request_id 用于幂等性:使用相同的 ID 和参数重复调用将返回第一次的结果,而不是执行两次操作。

工具

作用

accounts_list

别名、地址、状态、授予的权限。不含凭据。

accounts_status

每个账户的实时授权检查,以及邮箱总数。

gmail_search

Gmail 搜索语法;detail 为 ids、metadata 或 full;分页。

gmail_get_message

一条消息:发件人、收件人、抄送、密送、主题、时间戳、标签、已读状态、正文、附件清单。

gmail_get_thread

按顺序的整个对话,包含参与者。

gmail_attachments_list

附件清单。不下载任何内容。

gmail_attachments_get

获取字节:小附件内联 base64,否则写入网关自己的目录。

gmail_labels_list

所有标签及其计数,以及网关是否会修改每个标签。

gmail_labels_add

按 ID 或名称应用标签。拒绝 TRASH 和 SPAM。

gmail_labels_remove

按 ID 或名称移除标签。拒绝 TRASH 和 SPAM。

gmail_archive

移除 INBOX。邮件保留在“所有邮件”中;可逆。

gmail_mark_read

移除 UNREAD。

gmail_mark_unread

添加 UNREAD。

gmail_drafts_list

已保存的草稿,包含收件人、主题、片段。

gmail_drafts_get

完整的一封草稿。

gmail_drafts_create

新建纯文本草稿。保存,从不发送。

gmail_drafts_reply

在现有线程中创建回复草稿,包含正确的 In-Reply-To、References、主题和 threadId。

gmail_drafts_update

编辑草稿;省略的字段保留其值,线程信息保持不变。

变更操作适用于单个消息或线程,也适用于批处理(默认上限 100 个 ID)。标签可以以 ID(Label_7)或显示名称(Receipts)形式提供。

在关键地方,读写处理不对称:gmail_search 可以愉快地过滤 TRASH 或设置 include_spam_trash,因为检查已存在的内容是读取操作。而应用这些标签会被拒绝,因为那会将邮件移入垃圾桶或报告为垃圾邮件。

错误

失败以 MCP 工具错误形式返回,带有 isError: true 和结构化负载,同时出现在文本块和结构化内容中:

{"error": {
  "code": "forbidden_label",
  "message": "refusing to add label 'TRASH': moving messages to Trash is a forbidden capability of this gateway",
  "retryable": false
}}

错误码包括 invalid_input、unknown_account、not_found、too_large、batch_too_large、rate_limited、forbidden_operation、forbidden_label、account_read_only、needs_reauth、upstream_rate_limited、upstream_unavailable、network_error、timeout 和 internal_error。内部异常记录在服务器端,并报告为裸露的 internal_error——客户端永远不会收到回溯或内部路径。


管理

gmail-mcp-gateway accounts list
gmail-mcp-gateway accounts status               # live Gmail check per account
gmail-mcp-gateway accounts auth <alias>
gmail-mcp-gateway accounts reauth <alias>       # after a revoked or expired grant
gmail-mcp-gateway accounts remove <alias> --yes # revokes at Google, deletes locally

gmail-mcp-gateway token create <name>
gmail-mcp-gateway token list
gmail-mcp-gateway token revoke <name>

gmail-mcp-gateway audit --limit 50              # recent state-changing operations
gmail-mcp-gateway audit --account work --since-hours 24
gmail-mcp-gateway audit --outcome denied --json

gmail-mcp-gateway prune                         # expired audit rows, dedup keys, attachments
gmail-mcp-gateway health --json

在无头主机上,通过转发重定向端口进行授权:

# on the server
gmail-mcp-gateway accounts add work --no-browser --port 8899
# on your laptop
ssh -L 8899:127.0.0.1:8899 server
# then open the printed URL locally

审计日志记录账户、时间戳、操作、受影响ID、结果、错误码、持续时间和调用主体——包括成功、失败和拒绝。它绝不记录令牌、消息体、主题或附件内容。管理仅限CLI:被入侵的MCP客户端无法添加账户、触发同意流程、生成令牌或读取审计日志。

目录布局

配置、数据和秘密相互分离,且各自可覆盖,因此每个部分可以有不同的后端存储:

角色

变量

默认值

内容

配置

GMAIL_MCP_CONFIG_DIR

~/.config/gmail-mcp-gateway

config.toml — 不含秘密

数据

GMAIL_MCP_DATA_DIR

~/.local/share/gmail-mcp-gateway

gateway.db, attachments/

秘密

GMAIL_MCP_SECRETS_DIR

<data>/secrets

master.key, oauth_client.json, credentials/, gateway_tokens.json

config.toml 是可选的;所有键及其默认值请参见 deploy/config.example.toml —— 包括批量上限、页面大小、正文和附件预算、速率限制、重试策略以及幂等窗口。


边界如何强制执行

四个独立层。每一层单独即可阻止发送;只有全部四层都失效,消息才会离开。

1. 工具表面。 共有十八个工具。没有 gmail_send、没有 gmail_trash、没有 gmail_raw_request,也没有任何接受URL、路径、HTTP方法或端点名称的工具。通用Gmail代理不是客户端可以达到的,因为它根本不存在。 mcpsrv/server.py

2. 端点白名单。 每个对Gmail的HTTP请求必须指定一个十四个 Endpoint 常量之一。users.messages.send、users.drafts.send、users.messages.trash、users.messages.delete 以及 users.settings 下的所有内容均被排除。路径参数使用严格ID模式验证,并以空安全集进行百分号编码,因此任何值都无法引入/从而到达不同端点。在请求发出前,黑名单会再次检查解析后的方法和路径,独立于其构建方式。DELETE 和 PATCH 完全无法发出。 gmail/allowlist.py

3. 标签策略。 这关闭了白名单留下的后门。users.messages.modify 是允许的——它是归档和已读状态的工作方式——但Gmail将 TRASH 和 SPAM 视为普通标签,因此应用其中一个标签会删除邮件或将其报告为垃圾邮件。变异中的每个标签ID都会进行双向、不区分大小写的检查,并且在传输前还会再次检查组装后的请求体。 gmail/labels.py

4. OAuth 范围。 账户使用 gmail.modify 授权,别无其他。该范围无法永久删除邮件(messages.delete 需要 https://mail.google.com/),也无法触碰任何Gmail设置,因此转发规则、过滤器、POP/IMAP配置和永久删除在Google的授权层即被阻止,而不仅仅是在此处被拦截。Google没有发布任何允许创建草稿但不允许发送的范围,因此发送由第1-2层阻止。使用 --read-only 添加的账户获得 gmail.readonly,Google本身会拒绝所有写入操作。


安全模型

邮件内容不可信。 正文、主题、发件人名称和附件文件名由第三方编写,可能包含针对读取邮件的模型的指令。网关将每个读取结果标记为 content_is_untrusted: true,并且服务器指令告诉客户端将邮件视为数据而非指令。更有用的是,注入指令可能请求的能力并不存在。

HTML 从不执行,也从不作为标记返回。 <script>、<style>、<iframe> 及类似元素及其内容会被丢弃;所有其他标签被剥离。结果为纯文本。

不可见 Unicode 被剥离。 零宽字符、双向覆盖和Unicode标签字符可让攻击者向人类展示一个内容,而LLM读取另一个内容。这些字符被移除,移除数量记录为 removed_hidden_characters。

附件被存储,但从不打开。 网关不解析、渲染或执行附件内容。客户端可以建议文件名,但绝不能建议路径:目标始终为 <attachments_dir>/<account>/<message_id>/<sanitized-name>,经过解析并重新检查是否包含在内,以 O_NOFOLLOW 模式 0600 写入。

凭据永远不会到达客户端。 刷新令牌、访问令牌和OAuth客户端密钥仅存在于网关进程内部。每个账户的凭据使用 AES-256-GCM 在按账户派生的密钥(HKDF-SHA256(master, "…account:<id>"))下加密,账户ID作为关联数据——因此一个账户的密钥无法打开另一个账户的凭据,并且移动到不同账户的凭据文件将解密失败。文件权限为 0600,位于 0700 目录中;网关拒绝读取组或世界可读的密钥。

诚实的作用域: 静态加密保护备份、散落副本和磁盘镜像。它无法防御已经以网关用户身份执行代码的攻击者——该攻击者可以读取主密钥。文件系统权限仍然是主要边界。

日志不会泄露秘密。 每条日志记录都会经过一个过滤重写,任何看起来像Google访问或刷新令牌、客户端密钥、Bearer头、JWT或凭据名称字段的内容——在消息、参数和异常文本中——都会被重写。在 stdio 下,日志发送到 stderr,因为 stdout 是 MCP 线路。

输入经过验证。 草稿收件人必须是符合严格模式的裸地址;标头值中的任何 CR、LF 或 NUL 都被视为标头注入尝试而被拒绝。草稿从类型化字段组装——网关从不接受来自客户端的原始 RFC 5322。批量、页面大小、正文长度、附件大小和收件人数均有上限,并且每个账户的令牌桶在失败时快速返回带有 retry_after_seconds 提示,而不是排队。

这不能防范什么

  • 能够以网关用户身份执行代码的操作员。

  • 客户端合法使用允许的功能但使用不当——例如批量归档或编写误导性草稿。归档和标记是可逆且可审计的;草稿仍然需要人工发送。

  • Google 端被入侵或恶意的 OAuth 客户端配置。

  • 如果在没有 TLS 的情况下暴露 HTTP 传输,则可能被流量拦截。请将其保留在回环接口上,或在其前面放置一个 TLS 终止代理。


可靠性

  • 令牌刷新:自动,每个账户有锁,因此并发调用只会刷新一次。401 触发恰好一次刷新和重试。

  • 刷新失败:invalid_grant 将账户标记为 needs_reauth,并返回结构化错误,指出修复该问题的 CLI 命令。

  • 速率限制和 5xx:指数退避,完全抖动,遵循 Retry-After,最多 max_attempts 次。

  • 网络故障和超时:重试,然后报告为 network_error 或 timeout,不包含内部细节。

  • 分页:next_page_token 返回给客户端,因此服务器中不保存游标状态。

  • 重复请求:client_request_id 在 24 小时内抑制重复请求。并发重复请求在进程内序列化;使用不同参数重用 ID 是错误,而不是静默返回错误答案。

  • 背压:Gmail 调用共享可配置的并发信号量,每个账户有独立的令牌桶。因此,大型搜索或繁忙的账户不会产生无限制的上游并发。

扩展和部署拓扑

对于给定的数据和秘密目录,运行一个网关进程。SQLite 状态、凭据文件、令牌刷新锁和进行中的幂等协调有意保持本地;将多个副本指向同一卷不会提供安全的主主操作。

对于更大的安装,将账户分片到独立的网关实例中,每个实例有自己的配置、数据、秘密、Bearer 令牌和回环端口。这使故障、速率限制、审计轨迹和凭据保持隔离,同时允许每个实例服务并发客户端。仅在观察 Gmail 配额使用和主机容量后增加 limits.max_concurrency;默认值 8 是保守的。如果客户端需要一个共享的网络地址,在其前面放置一个 TLS 认证的路由层,并将每个账户别名路由到其自己的实例。

同一账户的主主副本需要将 SQLite 和本地凭据/幂等状态替换为协调的外部存储。这超出了当前网关的安全模型;不要仅仅通过增加工作节点或共享卷来扩展它。


测试

uv sync --all-extras
uv run pytest -q                                    # 334 tests, no Google account needed
uv run pytest tests/test_security_boundary.py -v    # just the guarantee

test_security_boundary.py 通过模拟 Gmail 驱动每个支持的操作,如果请求了禁止的 URL 则失败,然后尝试通过每个可用路由进行删除、垃圾邮件和发送。

一旦账户授权,用真实邮箱进行测试。该脚本像 MCP 客户端一样通过 stdio 连接,执行只读遍历,然后确认禁止的操作被拒绝:

uv run python scripts/try-it.py --account personal
uv run python scripts/try-it.py --account personal --draft    # also drafts a reply
uv run python scripts/try-it.py --account personal --archive  # archive round trip

除非传递了变异标志,否则为只读,并且它所做的每个变异都是可逆的。它创建的草稿必须由您删除——网关无法删除。

要交互式点击:

npx @modelcontextprotocol/inspector .venv/bin/gmail-mcp-gateway serve --transport stdio

布局

src/gmail_mcp_gateway/
├── mcpsrv/server.py      the tool surface — the complete client-facing API
├── mcpsrv/http.py        Streamable HTTP transport, bearer auth, bind safety
├── service.py            the supported operations, and nothing else
├── gmail/allowlist.py    the endpoint allowlist  ← security boundary
├── gmail/labels.py       label policy (blocks TRASH/SPAM)  ← security boundary
├── gmail/client.py       the only code that talks to Gmail
├── gmail/parse.py        MIME → structured data, sanitization
├── gmail/compose.py      draft assembly from typed fields
├── security/             validation, rate limiting, path confinement
├── auth/oauth.py         OAuth 2.0 + PKCE, refresh, revoke
├── accounts.py           account registry
├── crypto.py             envelope encryption for credentials
├── audit.py              audit log
└── cli.py                administration

添加一个支持的操作意味着:allowlist.py 中的 Endpoint,service.py 中的方法,mcpsrv/server.py 中的工具,EXPOSED_TOOLS 中的条目,以及测试。EXPOSED_TOOLS 和 FORBIDDEN_TOOLS 根据运行中的服务器进行断言,因此添加一个未声明的工具——或添加一个禁止的工具——会导致测试失败。保持网关专注于 Gmail;不同的 Google 产品应属于单独的 MCP 服务,而不是在此处使用更宽的范围。


故障排除

no OAuth client configured —— 快速入门第 3 步。放置 secrets/oauth_client.json(模式 0600)或设置 GMAIL_MCP_OAUTH_CLIENT_ID 和 GMAIL_MCP_OAUTH_CLIENT_SECRET。

Google did not return a refresh token —— 您之前已授权此应用。在 https://myaccount.google.com/permissions 移除其访问权限,然后重新运行 accounts auth <alias>。

consent screen did not grant every required permission —— 权限框未勾选。重新运行授权并保持所有框勾选。网关在此处故意失败,而不是留下一个半功能账户。

账户每周进入 needs_reauth 状态 —— OAuth 应用仍处于“测试”状态,Google 会在 7 天后过期刷新令牌。请发布它(快速入门第 2.4 步)。

stored credential failed authentication —— GMAIL_MCP_MASTER_KEY 已更改,或密钥文件已被替换。恢复原始密钥,或对每个账户运行 accounts reauth <alias>。

<file> is accessible to other users —— 网关拒绝读取组或世界可读的秘密。对该文件执行 chmod 600。

refusing to bind …: it is not a loopback address —— 有意如此。绑定 127.0.0.1 并使用 SSH 隧道,或者如果确实是受信任的私有接口,则设置 GMAIL_MCP_ALLOW_REMOTE_BIND=true。互联网可路由地址无论何种情况都会被拒绝。

serve --transport stdio 看起来挂起 —— 正确;它在等待 stdin 上的 JSON-RPC。让您的 MCP 客户端启动它,或使用 scripts/try-it.py。

有人更改了邮箱,我想知道具体发生了什么—— gmail-mcp-gateway audit --limit 50。每一次状态变更都记录在那里,拒绝操作也包含在内。

许可证

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Read-only MCP server for IMAP email access, enabling AI agents to read, search, and monitor email without sending or deleting messages.
    13 npm
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    MCP server that gives an AI assistant simultaneous access to multiple Gmail accounts with per-account read-only, draft, or send tiers, two-phase write confirmation, and safeguards for handling untrusted email content.
    10
    9 npm
    1
    MIT