connector-googlemail
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesegooglemail-client
googlemail-client
Motoko bindings for the Gmail API v1,
generated from Google's official OpenAPI spec.
Send-focused PoC surface: ,
, ,
. All 8 operations live in .
gmail_users_messages_sendgmail_users_drafts_{create,send,get,list}gmail_users_messages_{get,list}gmail_users_getProfileApis/UsersApi.mo这是针对Gmail API v1的Motoko绑定,由Google官方OpenAPI规范生成。
以发送为核心的PoC接口:、、、。所有8个操作都位于中。
gmail_users_messages_sendgmail_users_drafts_{create,send,get,list}gmail_users_messages_{get,list}gmail_users_getProfileApis/UsersApi.moTrigger phrases
触发场景
Reach for this skill on any request mentioning: send email, send message,
compose email, Gmail, draft, inbox, mailbox, "email the user", "notify via
email", "forward results by email", "send a notification email".
当请求中提及以下内容时,可使用此技能:发送邮件、发送消息、撰写邮件、Gmail、草稿、收件箱、邮箱、“给用户发邮件”、“通过邮件通知”、“通过邮件转发结果”、“发送通知邮件”。
How Gmail authentication works (read before wiring)
Gmail认证机制(集成前请阅读)
Gmail uses OAuth 2.0 Authorization Code flow — there is no static API key.
The canister never mints a token on its own; the user completes an OAuth dance
off-chain and passes in the resulting Bearer access token at call time.
Token lifetime: 1 hour by default (Google's access tokens). After expiry
the API returns HTTP 401. The refresh token must be exchanged off-chain too —
the canister cannot call Google's token endpoint (that would expose the client
secret on-chain). Surface a result and ask the caller
to re-authenticate.
#Err("auth_expired")Required OAuth 2.0 scope for : .
For read access add: .
messages.sendhttps://www.googleapis.com/auth/gmail.sendhttps://www.googleapis.com/auth/gmail.readonlyGmail采用OAuth 2.0授权码流程——没有静态API密钥。Canister不会自行生成令牌;用户需在链下完成OAuth流程,并在调用时传入生成的Bearer访问令牌。
令牌有效期:默认1小时(Google的访问令牌)。过期后API会返回HTTP 401。刷新令牌也必须在链下交换——canister无法调用Google的令牌端点(这会在链上暴露客户端密钥)。需返回结果,并请求调用方重新认证。
#Err("auth_expired")messages.sendhttps://www.googleapis.com/auth/gmail.sendhttps://www.googleapis.com/auth/gmail.readonlyUsage
使用示例
motoko
import { gmail_users_messages_send; gmail_users_getProfile;
gmail_users_drafts_create; gmail_users_drafts_send }
"mo:googlemail-client/Apis/UsersApi";
import { Message; type Message } "mo:googlemail-client/Models/Message";
import { Draft; type Draft } "mo:googlemail-client/Models/Draft";
import { defaultConfig } "mo:googlemail-client/Config";
import Text "mo:core/Text"; // Text.encodeUtf8: build the raw RFC 2822 Blob
// Shared cfg — swap in the caller's short-lived bearer token.
let cfg = {
defaultConfig with
auth = ?#bearer "<off-chain OAuth2 access token>";
max_response_bytes = ?500_000;
is_replicated = ?false; // non-replicated: required for sends (see Notes); reads too
};
// Send a message. `raw` is the PLAIN RFC 2822 message as a Blob — the client
// base64-encodes it for the Gmail API; do NOT base64-encode it yourself.
let mime : Text = "From: me\r\nTo: friend@example.com\r\nSubject: Hi\r\n\r\nHello!";
let outMsg = Message.init {}; // all-null base, then layer fields:
let envelope : Message = { outMsg with raw = ?Text.encodeUtf8(mime) };
let result = await* gmail_users_messages_send(cfg,
"me", // userId: "me" = authenticated user
#_1_, // $.xgafv — use #_1_ (v1) for all calls
"", // accessToken — leave "" when auth = ?#bearer above
#json, // alt
"", "", "", "", true, "", "", "", // callback/fields/key/oauthToken/prettyPrint/quotaUser/uploadProtocol/uploadType
envelope
);
// Get the authenticated user's email address
let profile = await* gmail_users_getProfile(cfg, "me",
#_1_, "", #json, "", "", "", "", true, "", "", "");
let ?email = profile.emailAddress else return #Err("no email");motoko
import { gmail_users_messages_send; gmail_users_getProfile;
gmail_users_drafts_create; gmail_users_drafts_send }
"mo:googlemail-client/Apis/UsersApi";
import { Message; type Message } "mo:googlemail-client/Models/Message";
import { Draft; type Draft } "mo:googlemail-client/Models/Draft";
import { defaultConfig } "mo:googlemail-client/Config";
import Text "mo:core/Text"; // Text.encodeUtf8: build the raw RFC 2822 Blob
// Shared cfg — swap in the caller's short-lived bearer token.
let cfg = {
defaultConfig with
auth = ?#bearer "<off-chain OAuth2 access token>";
max_response_bytes = ?500_000;
is_replicated = ?false; // non-replicated: required for sends (see Notes); reads too
};
// Send a message. `raw` is the PLAIN RFC 2822 message as a Blob — the client
// base64-encodes it for the Gmail API; do NOT base64-encode it yourself.
let mime : Text = "From: me\r\nTo: friend@example.com\r\nSubject: Hi\r\n\r\nHello!";
let outMsg = Message.init {}; // all-null base, then layer fields:
let envelope : Message = { outMsg with raw = ?Text.encodeUtf8(mime) };
let result = await* gmail_users_messages_send(cfg,
"me", // userId: "me" = authenticated user
#_1_, // $.xgafv — use #_1_ (v1) for all calls
"", // accessToken — leave "" when auth = ?#bearer above
#json, // alt
"", "", "", "", true, "", "", "", // callback/fields/key/oauthToken/prettyPrint/quotaUser/uploadProtocol/uploadType
envelope
);
// Get the authenticated user's email address
let profile = await* gmail_users_getProfile(cfg, "me",
#_1_, "", #json, "", "", "", "", true, "", "", "");
let ?email = profile.emailAddress else return #Err("no email");Notes
注意事项
- Use (non-replicated) for sends (
is_replicated = ?false,gmail_users_messages_send). These outcalls are non-idempotent and Gmail's response is non-deterministic (unique messagegmail_users_drafts_send, per-requestidheader). In replicated mode (Date) every subnet replica issues the request — so the email is sent once per replica (duplicates) and the differing responses fail IC consensus ("No consensus could be reached. Replicas had different responses"). Non-replicated has a single node perform exactly one send. Reads (null,gmail_users_messages_list,gmail_users_messages_get) also usegmail_users_getProfile(one node, ~13× cheaper).?false - The parameter (version discriminator) should always be
$.xgafvfor Gmail API v1 calls. The#_1_parameter should always bealt.#json - All optional string parameters (,
callback,fields,key,oauthToken,quotaUser,uploadProtocol) acceptuploadTypeto omit them;""can beprettyPrint.false - =
userIdrefers to the authenticated user. Explicit email addresses also work but require the"me"scope.https://mail.google.com/ - Messages must be in RFC 2822 format, passed as a plain in the
Blobfield (e.g.raw). The client base64-encodes?Text.encodeUtf8(mime)for the API — do not base64-encode it yourself (that double-encodes and Gmail rejects it). Theraw/Message.payloadfields are for parsed read responses — don't try to build them for sending.MessagePart - Google returns HTTP 429 on rate-limit (quota exceeded). Surface the error to the caller; never silently retry inside the canister — a send retry may deliver duplicates.
- Access tokens expire in 1 hour. On 401, surface so the caller can re-authenticate off-chain and retry with a fresh token.
#Err("auth_expired") - holds a
Draft.message;Messagebuilds a draft server-side. Usegmail_users_drafts_createto send a draft by itsgmail_users_drafts_send.id - : Gmail message reads can be large. 500 KB covers typical messages; bump to 2 MB for messages with large payloads (Gmail API's max response is bounded by the API, but set conservatively for cycle budgets).
max_response_bytes - Cycle budget: (30B). On the IC, outbound HTTPS calls cost ~10–15B cycles for a typical send. Adjust if you see
defaultConfig.cycles = 30_000_000_000errors.InsufficientCycles
- 发送操作需设置(非复制模式)(
is_replicated = ?false、gmail_users_messages_send)。这些出站调用是非幂等的,且Gmail的响应是非确定性的(每个请求有唯一的消息gmail_users_drafts_send和id头)。在复制模式(Date)下,每个子网副本都会发起请求——因此邮件会每个副本发送一次(重复发送),且不同的响应会导致IC共识失败(“无法达成共识。副本响应不一致”)。非复制模式下仅由单个节点执行一次发送操作。读取操作(null、gmail_users_messages_list、gmail_users_messages_get)也建议使用gmail_users_getProfile(单个节点执行,成本约低13倍)。?false - 参数(版本标识符)在Gmail API v1调用中应始终设置为
$.xgafv。#_1_参数应始终设置为alt。#json - 所有可选字符串参数(、
callback、fields、key、oauthToken、quotaUser、uploadProtocol)可传入uploadType来省略;""可设为prettyPrint。false - =
userId代表已认证用户。也可使用明确的邮箱地址,但需要"me"权限范围。https://mail.google.com/ - 消息必须采用RFC 2822格式,以**纯**形式传入
Blob字段(例如raw)。客户端会为API对?Text.encodeUtf8(mime)进行base64编码——请勿自行进行base64编码(这会导致双重编码,Gmail会拒绝该消息)。raw/Message.payload字段用于解析读取响应——请勿尝试构建这些字段用于发送。MessagePart - 当超出配额限制时,Google会返回HTTP 429。需将错误反馈给调用方;切勿在canister内部静默重试——重试发送可能会导致重复投递。
- 访问令牌1小时后过期。遇到401错误时,返回,以便调用方在链下重新认证并使用新令牌重试。
#Err("auth_expired") - 包含一个
Draft.message;Message用于在服务器端创建草稿。使用gmail_users_drafts_create通过草稿gmail_users_drafts_send发送草稿。id - :读取Gmail消息可能会返回较大的数据。500 KB足以覆盖典型消息;对于包含大负载的消息,可将其提升至2 MB(Gmail API的最大响应受API限制,但需为周期预算保守设置)。
max_response_bytes - 周期预算:(300亿)。在IC上,出站HTTPS调用的典型发送成本约为100-150亿周期。如果遇到
defaultConfig.cycles = 30_000_000_000错误,请调整该值。InsufficientCycles