邮箱验证码 API:用 Python 在测试中接收验证码
在 Python 脚本里读取邮箱验证码,归结起来就是三个 HTTP 请求:租用邮箱、轮询直到验证码出现、不想等了就取消订单。这篇教程只用 requests 和标准库,通过 OTPGmail 的 REST API 把这个流程搭起来。说明用中文,代码保持英文。
这个示例是写给测试自家注册流程的开发者和 QA 团队的。OTPGmail 的邮箱是真实的 @gmail.com 或 @icloud.com 邮箱,只接收邮件,不接收短信。如果你在第三方平台上注册账号,请遵守该平台的条款。
开始之前
- API 密钥:每个 OTPGmail 账户都有一个以
og_开头的个人 API 密钥,登录后可以在账户页面或 API 文档页面查看。请求时放在Authorization: Bearer <密钥>请求头里。 - 余额:租用费用从余额扣除,分配到邮箱才扣费。
- 文档:所有接口和字段见 API 文档。
- 服务代码:
GET /v1/services返回每个服务的代码、价格、库存和 iCloud 信息。例如 GitHub 的代码是git,ChatGPT 是dr。
流程:三个请求
- 用
POST /v1/orders为指定服务租用一个地址。 - 每 3–5 秒调用一次
GET /v1/orders/{orderId};otp数组里出现内容,就说明验证码到了。 - 不想等了,调用
POST /v1/orders/{orderId}/cancel;第一个验证码到达前取消,费用 100% 退回。
就算你什么都不做,没有收到验证码的订单也会在等待时间(最长 25 分钟)结束后自动取消并退款。所以第三步不是必需的,只是让余额更早回来。
租用邮箱
import time
import uuid
import requests
HOST = "otpgmail.net"
BASE = f"https://{HOST}/v1"
HEADERS = {"Authorization": "Bearer og_YOUR_API_KEY"}
def rent(service, domain="gmail.com"):
resp = requests.post(
f"{BASE}/orders",
json={"service": service, "domain": domain},
headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
timeout=15,
)
body = resp.json()
if not body.get("success"):
raise RuntimeError(body["error"]["code"])
return body["data"][0] # data 是数组:每个邮箱一个订单
domain 是可选参数,不传就是 gmail.com;服务提供 iCloud 时可以传 icloud.com。Idempotency-Key 请求头很重要:每个新订单生成一个新的 UUID,超时后重试同一个订单时沿用同一个密钥,这样就不会为第二个邮箱付费。创建订单的请求在高峰期可能要几秒钟,客户端超时请设为 15 秒左右。
请用 requests,不要用标准库的 urllib:urllib 默认的 User-Agent 可能会被 API 前面的 CDN 拒绝。
等待验证码与取消
def wait_for_code(order_id, max_wait=600, interval=4):
deadline = time.time() + max_wait
while time.time() < deadline:
resp = requests.get(f"{BASE}/orders/{order_id}", headers=HEADERS, timeout=15)
order = resp.json()["data"]
if order["otp"]:
return order["otp"][-1]["code"] # 最新的验证码在数组末尾
if order["status"] == "cancelled":
return None # 等待时间已过,费用已退回
time.sleep(interval)
# 到了自己设定的上限:在第一个验证码前取消并退款
requests.post(f"{BASE}/orders/{order_id}/cancel", headers=HEADERS, timeout=15)
return None
3–5 秒的间隔既能及时拿到验证码,又远低于速率限制。otp 数组按到达顺序排列,最早的在前,所以 otp[-1] 永远是最新的验证码。有些服务发的不是数字验证码,而是验证链接,这时链接也会出现在 code 字段里。如果该服务会发送多个验证码,后续的验证码会在订单有效期内自动追加到数组末尾;很多服务只发一个。
串起来
try:
order = rent("git", domain="icloud.com")
except RuntimeError as e:
if str(e) in ("DOMAIN_UNAVAILABLE", "NO_MAILS_AVAILABLE", "OUT_OF_STOCK"):
order = rent("git", domain="gmail.com") # 换 Gmail 再试
else:
raise
print("地址:", order["email"])
# 把这个地址填进注册表单并提交,然后开始等待
code = wait_for_code(order["orderId"])
print("验证码:", code or "没有收到")
先把地址填进目标网站的表单、请求发送验证码,再开始等待;表单没提交,验证码自然不会来。Gmail 和 iCloud 的区别见接收验证码用 Gmail 还是 iCloud,GitHub 的具体情况见 GitHub 说明页。
用 curl 走一遍
HOST=otpgmail.net
KEY=og_YOUR_API_KEY
# 创建订单
curl -s -X POST "https://$HOST/v1/orders" \
-H "Authorization: Bearer $KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"service":"git","domain":"gmail.com"}'
# 查询订单(每 3-5 秒一次)
curl -s "https://$HOST/v1/orders/ORDER_ID" -H "Authorization: Bearer $KEY"
# 在第一个验证码前取消
curl -s -X POST "https://$HOST/v1/orders/ORDER_ID/cancel" -H "Authorization: Bearer $KEY"
错误码
NO_MAILS_AVAILABLE、OUT_OF_STOCK:该服务暂时没有可用邮箱。不扣费;稍等后重试,或者换一个domain。DOMAIN_UNAVAILABLE:该服务不提供 iCloud,或暂时关闭;改用gmail.com。INSUFFICIENT_BALANCE:余额不足,请先充值。VALIDATION_ERROR:请求字段有误;用GET /v1/services核对服务代码。ORDER_NOT_CANCELLABLE:第一个验证码已经到达,或者该服务的规则不允许取消。RATE_LIMITED(HTTP 429):超过了每个密钥的请求限制(默认每秒 20 次、每分钟 900 次),请按Retry-After请求头给出的时间等待后再试。
可选参数 warehouse 决定订单用 GET /v1/services 返回的 offers[] 中的哪一项:0 表示有库存且最便宜的一项,99 表示自动选择,缺货时换下一项。用 99 时如果全部都租不到邮箱,会返回 ALL_WAREHOUSES_EXHAUSTED,同样不扣费。
测试自动化建议
- 每个测试一个订单:把订单 ID 写进测试日志,排查问题时能知道用的是哪个地址。
- 清理阶段取消订单:测试在收到验证码之前结束,就取消订单;第一个验证码前取消的订单 100% 退款。
- 合理的等待上限:
max_wait设得比等待时间还长没有意义,到时系统会自动关闭订单。 - 并行测试:一次请求可以用
quantity字段最多租 200 个邮箱,同一账户最多 500 个订单同时等待验证码;并行轮询时注意速率限制。 - 批量需求:大量地址也可以在网页上用批量租用购买;Gmail 批次中每个邮箱都有一个不需要 API 密钥的验证码链接。
Python、Node.js、PHP、C# 和 curl 的现成客户端,都在公开的 otpgmail-email-otp-api 仓库里。完整的退款规则见退款与质保政策,获取 API 密钥请登录应用。
在 CI 和测试环境中使用
- 密钥不要写进代码:把 API 密钥放在环境变量或 CI 的密钥管理里,脚本运行时再读取;密钥泄露时,在 API 文档页面生成新密钥,旧密钥立即失效。
- 控制花费:测试账户只充一小笔余额,用
GET /v1/balance在每次运行前检查余额,余额不足就跳过需要真实邮箱的用例。 - 区分失败原因:把“没有可用邮箱”“等待超时”“验证码错误”分别记录。前两种通常与被测系统无关,不该让整个测试判定为失败;第三种才更可能是你自己的注册流程出了问题。
常见问题
为什么需要 Idempotency-Key 请求头?
为了在连接中断、请求被重发时,不会再租一个邮箱。只有重试同一个订单时才复用同一个密钥,每个新订单都要生成新的 UUID。
max_wait 应该设多少?
取决于服务:GitHub 的验证码通常约 1 分钟就到,ChatGPT 通常要约 1 分钟,有时更久。设得比等待时间还长没有作用,到时系统会自动取消订单并退款。
一次请求可以租多个邮箱吗?
可以,在请求体里加上 quantity 字段,最多 200 个。这时 data 数组里每个邮箱对应一个订单。
验证码到达前,otp[-1] 会返回什么?
在第一个验证码到达之前,otp 是空数组,直接取 otp[-1] 会报错。示例里的 if order["otp"]: 判断正是为了避免这种情况。
