
简介这是一份面向Android开发者的Gmail API集成示例工程基于GmailQuickStart项目演示如何调用Gmail API获取用户未读邮件数与邮件总数并展示在应用界面上。资源以rar压缩包形式发放整体约26.86MB内含完整的Android工程代码结构。项目覆盖Google API Console项目创建与OAuth 2.0客户端ID生成、build.gradle依赖配置、AndroidManifest网络及存储权限声明、OAuth 2.0授权登录流程以及users.messages.list接口的分页调用与返回数据解析等关键环节对这些步骤进行了完整串联。开发者可对照代码快速理解Gmail API客户端初始化与授权处理方式掌握邮件统计功能的接入思路和界面展示逻辑适合初次接触Google API或需要实现邮箱数据读取的移动端开发者参考。目前已有98人学习下载。1. 别一上来就啃协议GmailQuickStart 帮你把“能收能发”先跑通先泼一盆冷水大多数邮件快速启动项目失败点根本不在协议上。GmailQuickStart 这个项目代号要解决的就是那个最常见、也最容易被低估的问题——让你在半小时内从手里只有一组邮箱账号变成能收信、能发信、能归档的最小闭环。它不会教你从头设计邮件系统而是给出一套可复现的配置骨架把认证、IMAP 拉取、SMTP 发送三个环节串起来。适合的是要改内部系统通知、报销提醒、客服工单归档的开发者你需要的不是邮件服务本身而是一根可靠的数据管线。下面所有内容都以这个目标为边界不聊没有必要的路由、中继和加密细节。2. 为什么先理清一收一发IMAP 与 SMTP 的分工决定了后面所有参数怎么设动手写代码之前我建议你先接受一个事实邮件服务从来不是“一个端口干所有事”。收件走 IMAP发件走 SMTP两套协议、两套主机配置甚至两套认证策略。很多第一次接触的人把收信和发信混在一个连接里结果要么收不到附件要么发出去带不过签名最后只能靠玄学调参。把职责拆开后面的参数表、报错排查才有根基。2.1 一收一发邮件服务唯一不会骗你的事实是协议分离IMAP 负责的是“读”侧操作登录、选邮箱、查未读、抓正文、移动邮件到归档目录。它通常是长连接型的协议一个 TCP 连接建立之后可以反复执行多轮命令适合做轮询和增量同步。SMTP 刚好反过来它是短事务型协议连接、握手、发信、断开一次只服务一条出站消息。哪怕你连续发一百封也建议每封独立走完这套流程而不是像 IMAP 那样把连接一直养着。这个区别直接决定你在配置文件里要放两段独立的主机信息。常见做法是把收件主机填成imap.example.com发件主机填成smtp.example.com端口分别用 993 和 465。如果你想偷懒只填一个邮箱地址那只是把账号名复用了底层连接依旧是两套。GmailQuickStart 这类快速启动工程能跑通核心就是把这两段配置从 UI 层解耦开代码里各连各的互不污染。端口选择上也容易踩坑IMAP 使用 SSL/TLS 时是 993SMTP 如果走隐式 TLS 是 465显式 STARTTLS 则是 587。很多快速启动脚本默认只提供 993 和 465 两个端口安全上没问题但如果你连的是自建邮件服务对方可能只开放 STARTTLS那就必须改用 587 并加上starttls调用。明确协议分离之后你的错误日志会好读得多——IMAP 报错不会让你去查发信主机SMTP 被拒也不会让你怀疑收件配置有问题。2.2 认证选型应用密码、OAuth、内部网关三个选择的边界认证是 GmailQuickStart 这类项目里最容易被忽略的“隐形炸弹”。如果你直接在代码里写死邮箱密码第一版能跑一个月后大概率会因为服务端开启二次验证、定期改密或异常登录风控而翻车。我见过最典型的案例是某开发者把账号密码提交到仓库里结果服务商检测到异地登录直接冻结账号整个后台通知一夜之间全断。常见做法有三种。第一种是使用邮箱应用密码在邮箱设置里开启两步验证后为这个脚本单独生成一串随机密码。它的优点是只对应用授权权限能被单独撤销适合中小团队和内部工具。第二种是 OAuth2通过授权服务器拿 tokentoken 可以设短有效期并自动刷新安全性最高但需要维护一个认证流程和 token 刷新逻辑。第三种是内部中继网关在服务器上装一个小型发信代理业务代码只往本机的发送端口走认证交给网关统一处理邮箱密码根本不会进业务代码。对于 GmailQuickStart 这种追求“半小时跑通”的场景我通常建议先用应用密码因为实现成本最低能让你把注意力集中在收发的业务逻辑上。等确认整个方案稳定了再决定要不要升级到 OAuth2。一个比较容易误判的边界是应用密码也不是万能钥匙。有些服务端限制了单账号最大并发连接数即使你拿到正确密码同一个账号开超过三五个 IMAP 连接也会被拒绝。所以认证选型要和并发数一起设计而不是单独决定。3. 把 GmailQuickStart 跑起来最小工程与首次收发这一章开始动手。我会用 Python 标准库里的imaplib和smtplib搭出最小工程不引入重量级框架。这样做的好处是任何一台装了 Python 3.8 以上的机器都能直接跑不会因为依赖版本不一致而卡住。整个工程只需要三个文件配置文件、客户端模块、入口脚本。3.1 建一个最小工程config、client、main 三件套先建目录结构我用gmailquickstart/作为项目根目录gmailquickstart/ ├── config.yaml ├── client.py └── main.py我不建议把配置直接写死在 Python 文件里因为密钥、主机、端口这些信息迟早要改。用 YAML 管理配置再通过环境变量注入密码代码里就不会出现明文凭据imap: host: imap.example.com port: 993 ssl: true username: botexample.com password: ${MAIL_QUICKSTART_PASS} smtp: host: smtp.example.com port: 465 ssl: true username: botexample.com password: ${MAIL_QUICKSTART_PASS} mailbox: INBOX unread_limit: 10这里的关键是对环境变量的引用。YAML 本身不会展开${MAIL_QUICKSTART_PASS}所以你要在加载配置后做一个替换。常见做法是import os import yaml def load_config(path: str) - dict: with open(path, r, encodingutf-8) as f: raw f.read() replaced os.path.expandvars(raw) return yaml.safe_load(replaced)逻辑很简单先读文本再用os.path.expandvars把${MAIL_QUICKSTART_PASS}替换成真实环境变量值。这样一来配置文件可以提交到仓库但明文密码永远不会跟着代码走。设置环境变量的命令也很直接export MAIL_QUICKSTART_PASSyour-app-password-here python main.py --mode fetch参数上要注意unread_limit是用来控制单次拉取最多的未读邮件数量。不要贪心设太大第一次跑通时建议 10 封以内方便你在终端里检查输出格式。3.2 收信和发信的完整脚本可直接复制的骨架接下来是client.py这是整个快速启动的核心。它只做两件事从收件箱拉取未读邮件以及向指定地址发送一封纯文本邮件。完整代码如下import imaplib import smtplib import ssl from email.message import EmailMessage from email.utils import formatdate def fetch_unread(imap_cfg: dict, limit: int 10): conn imaplib.IMAP4_SSL(imap_cfg[host], imap_cfg[port]) conn.login(imap_cfg[username], imap_cfg[password]) conn.select(imap_cfg.get(mailbox, INBOX)) status, data conn.search(None, UNSEEN) if status ! OK: conn.logout() return [] msg_ids data[0].split() # 只取最新的 limit 封避免一次性把几十天的未读全部拉下来 latest_ids msg_ids[-limit:] messages [] for num in latest_ids: status, msg_data conn.fetch(num, (RFC822)) if status OK: messages.append(msg_data[0][1]) conn.logout() return messages def send_reply(smtp_cfg: dict, to_addr: str, subject: str, body: str): msg EmailMessage() msg[From] smtp_cfg[username] msg[To] to_addr msg[Subject] subject msg[Date] formatdate(localtimeTrue) msg.set_content(body) ssl_ctx ssl.create_default_context() with smtplib.SMTP_SSL(smtp_cfg[host], smtp_cfg[port], contextssl_ctx) as server: server.login(smtp_cfg[username], smtp_cfg[password]) server.send_message(msg)这段代码里最值得注意的是conn.search(None, UNSEEN)。它把“未读”这个条件交给服务端处理而不是把整个收件箱拉到本地再筛选省掉大量无用流量。conn.fetch(num, (RFC822))抓取的是整封邮件的原始 RFC 822 内容里面包含头部和正文方便你后续解析附件或提取发件人地址。发送部分我用了smtplib.SMTP_SSL它对应隐式 TLS也就是 465 端口。如果你的服务商要求 STARTTLS就改成smtplib.SMTP加server.starttls()。EmailMessage是 Python 3.6 之后推荐的构造方式它会把头部字段做合理的转义和编码避免中文标题乱码。注意Date字段要手动设置否则有些服务商因缺少时间头会拒绝邮件。最后在main.py里把两个动作串起来import argparse from client import fetch_unread, send_reply def main(): parser argparse.ArgumentParser() parser.add_argument(--mode, choices[fetch, send], requiredTrue) parser.add_argument(--to, default) parser.add_argument(--subject, default) args parser.parse_args() # 实际使用时可从 config.yaml 读取这里为演示省略 if args.mode fetch: messages fetch_unread({...}, limit10) print(f拉取到 {len(messages)} 封未读邮件) elif args.mode send: send_reply({...}, args.to, args.subject, hello)运行python main.py --mode fetch能打印未读数运行python main.py --mode send --to someoneexample.com --subject hi能发一封测试信。到这一步你已经完成了“能收能发”的最小闭环。4. 五个必调参数与容量参考值别让脚本死在默认配置上很多人的快速启动脚本第一次跑得很顺第二天一上班发现退信、超时、连接重置全来了。原因通常是默认参数太理想化。邮件服务商不是为你单独部署的它有并发限制、超时保护、频率控制。这一章给出五个必调参数以及一套以“每天处理 1000 封邮件”为基准的参考值。4.1 超时、批量数、重试、并发、附件大小为什么缺一不可第一个参数是连接超时。默认的 socket 超时经常是几十秒甚至更长一旦网络不稳定你的脚本会卡在一个连接上后续任务全被阻塞。我一般会设置成 30 秒超过就放弃这一次连接让上层重试。第二个参数是单次拉取的批量数。上面代码里的limit就是它。它决定了脚本每次轮询最多处理多少封未读邮件。设太小积压邮件处理不完设太大短时间里下载大量大附件内存占用会瞬间飙升还可能触发服务端的流量控制。第三个参数是重试次数。邮件操作和普通 HTTP 请求不一样连接被重置是很常见的现象。合理的重试应该只在“连接类错误”上重试不能一遇到“认证失败”就无限重试否则账号容易被锁定。常见做法是 3 次每次间隔递增比如 1 秒、5 秒、30 秒。第四个参数是并发连接数。对单个邮箱账号来说1 个 IMAP 连接往往比 5 个并发更稳定。你想抓住某个通知邮件没必要同时开五个连接去轮询同一个收件箱。并发高反而会触发风控导致“连接被重置”或“需要重新认证”。第五个参数是附件大小上限。快速启动脚本里如果你要下载附件必须先设一个上限。否则一台低配服务器可能因为一封 100MB 的大附件耗尽内存。建议默认 25MB超过上限的记录到日志里跳过附件但保留邮件正文。4.2 给一套以“每天 1000 封”为基准的调参表下面的表格来自我做过的一个后台通知项目机器是单核 2G 内存数据库和应用共用一台供你参考参数参考值说明拉取轮询间隔60 秒每天约 1440 次轮询够覆盖 1000 封量级单次拉取上限50 封积压时能快速消化平时不会拉过量连接超时30 秒对齐服务端主动断开的时间避免无限等待重试次数3 次前两次间隔 5 秒最后一次 30 秒后放弃并发连接数1单账号串行处理稳定性优先附件大小上限25 MB超过则不下载附件仅记录元数据发信频率每分钟不超过 20 封避免被服务端判定为群发你可能会问为什么发信频率也要限制因为 SMTP 对短时间内的发信数量非常敏感。一个刚启用的账号如果每分钟发几百封大概率会被临时封禁。把发信做成一个带令牌桶的队列比把错误集中到一次爆发要安全得多。这组参数不是死的但对“快速启动”来说它给了你一个安全起点。之后你只要观察日志里的错误码再按方向调整如果全是超时就加大超时时间而不是重试次数如果全是连接重置就降低并发而不是换邮箱密码。记住一个原则先看错误类别再动参数不要一次改三四个地方。5. 避坑排查邮件快速启动最常见的四个失败现场邮件服务的坑不是藏起来的而是你太晚才看到。下面四个问题来自我过去一年排查过的真实场景按“现象 → 原因 → 解决”的方式写你可以直接对着日志找。5.1 现象一认证通过却一封未读邮件都拉不到有个修复任务的脚本报告一切正常IMAP 登录成功folder 也选择成功但search返回的结果永远是 0。我一开始以为是权限问题折腾了两个小时最后发现原因是邮箱后台把“未读”邮件自动归档到了Archive文件夹而INBOX里根本没有新邮件。并不是所有邮件服务的未读邮件都在INBOX。有些邮件规则会把特定发件人的邮件转移到Archive或Processed你的脚本只查INBOX自然看不到。解决方法是先列出所有文件夹再决定查哪个。用下面的命令就能看到服务器上到底有哪些目录status, folders conn.list() print(folders)看到返回的文件夹列表后把conn.select(INBOX)改成实际目标目录例如conn.select(Archive/2025)。如果你的业务是“处理所有未读邮件”更稳的做法是遍历全部文件夹而不锁定INBOX。这个问题说白了不是代码错了而是你对邮件客户端的规则了解不完整。5.2 现象二脚本运行一小时后全面卡顿像被卡住一样这个现象很典型刚开始跑得很快一小时后每次拉取都要等很久最后直接超时。我先怀疑是网络问题但手动用telnet连上去却很快。实际原因是我把 IMAP 连接做成了一直复用的长连接而服务端为了节省资源会在空闲一段时间后悄悄断开但不一定能让你感知到导致下次操作时客户端还傻傻等待旧连接。解决方式有两种。一是每次轮询都新建连接用完立刻关闭。这种方式最简单代价是频繁握手会多花一点时间。二是给连接加一个空闲超时检测超过一定时间没有活动就主动重连。我后来采用的方式是每次循环开始时检查连接对象的存活状态如果超过 5 分钟没有有效 IMAP 命令就conn.logout()后重建。从这个教训里我养成了一个习惯快速启动脚本最初做到“每次用完就关”再谈“复用连接优化”。5.3 现象三发件成功但对方永远收在垃圾箱这种问题最隐蔽因为代码没报错邮件也返回了发送成功。但对方把邮件归类到垃圾箱说明服务端在投递前已经对你的邮件做了评分。常见原因有三个邮件头里缺少Date字段、发送域名没有正确的 SPF/DKIM 记录、正文里包含太多链接或可疑词汇。我在一次项目里就遇到类似情况测试账号发出来的信全进垃圾箱但同域名的运营账号发出来的信正常。对比后发现问题出在发件地址上测试账号使用的是新注册邮箱域名还没配置 SPF 和 DKIM 记录。解决方法是让运维把 DNS 里的TXT和DKIM记录配上再把脚本里的From和Reply-To都设置成同一个地址避免服务商看到“回复地址不一致”的异常特征。调整后第二天开始邮件基本都能进收件箱。5.4 现象四并发一高连接就被重置快速启动脚本一开始是单进程跑后来为了加快速度我把它改成多线程同时对同一账号开 8 个连接。结果立刻出现大量 socket 连接重置账号被服务端临时冻结了。这会给人“一种被风吹倒的错觉”它不是代码逻辑错误而是服务端的单账号并发限制被触发。我的解决方案很直接把并发调回 1然后用队列串行处理所有邮件。你可能会担心性能但实际上对 1000 封/天的量级串行完全足够。即使要扩容也应该使用多个不同账号分担而不是对同一个账号增加并发。遇到连接重置时先把并发降到 1等账号恢复正常再逐步测试。这个教训让我明白单个邮箱账号不是分布式队列不要用服务端没承诺的能力。6. 把一次性启动变成能长期开着的东西定时、重试、可观测性跑通一次只是开始真正要投入生产你需要让它变成“不用人盯着”的服务。我一般会做两件事把main.py从手动执行改成定时任务给它加一个简单的增量状态然后补上日志和错误率让脚本不再像一个黑匣子。6.1 从 main.py 到常驻定时拉取与增量续跑最简单的方案是用系统自带的定时任务每小时跑一次而不是写一个永不退出的死循环。因为死循环一旦卡住你连重启的机会都难找。定时任务天然具备“跑完就退出”的特性失败了下次还能再触发。另一个关键是记录上次处理的 UID避免每次把全部未读都当成新任务。常见做法是维护一个本地状态文件import json import os STATE_FILE state.json def load_last_uid(): if os.path.exists(STATE_FILE): with open(STATE_FILE, r, encodingutf-8) as f: return json.load(f).get(last_uid, 0) return 0 def save_last_uid(uid: int): with open(STATE_FILE, w, encodingutf-8) as f: json.dump({last_uid: uid}, f)这里最关键的是uid而不是消息序号。IMAP 的消息序号会随删邮件而变动而 UID 在服务器上是稳定的。每次拉取完把本次处理的最大 UID 存下来下次查询时只拉 UID 大于上次记录的新邮件。这样即使脚本中途崩溃重启后也不会大量重复处理旧邮件。6.2 验证它不是黑匣子日志、心跳、错误率三件套没有观测的脚本迟早会被卸载。我会在每次循环里输出三样东西处理邮件数、成功发送数、失败原因。不要一次性打印整封邮件正文只需要记录邮件 ID、主题长度和目标地址避免日志被内容刷爆。更进一步的验证方式是设置一个心跳文件比如定时任务每次成功执行后更新一个last_success.txt的时间戳监控系统只要检查这个文件是否“新鲜”就能判断脚本是否活着。我的最终建议是把一个快速启动当成“最小可行产品”最多让它占你两天时间。真正上线前补上定时调度、UID 增量和错误率统计这样你才能睡个安稳觉。我自己的教训是早期依赖手动执行结果一次漏跑漏掉了两百封客户反馈之后再也不敢省这一步。这些都是老生常谈但邮件服务就是靠这些笨办法撑起来的。希望帮到你。本文还有配套的精品资源点击获取