connector-googlemail

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

googlemail-client

googlemail-client

Motoko bindings for the Gmail API v1, generated from Google's official OpenAPI spec.
Send-focused PoC surface:
gmail_users_messages_send
,
gmail_users_drafts_{create,send,get,list}
,
gmail_users_messages_{get,list}
,
gmail_users_getProfile
. All 8 operations live in
Apis/UsersApi.mo
.
这是针对Gmail API v1的Motoko绑定,由Google官方OpenAPI规范生成。
以发送为核心的PoC接口:
gmail_users_messages_send
gmail_users_drafts_{create,send,get,list}
gmail_users_messages_{get,list}
gmail_users_getProfile
。所有8个操作都位于
Apis/UsersApi.mo
中。

Trigger 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
#Err("auth_expired")
result and ask the caller to re-authenticate.
Required OAuth 2.0 scope for
messages.send
:
https://www.googleapis.com/auth/gmail.send
. For read access add:
https://www.googleapis.com/auth/gmail.readonly
.
Gmail采用OAuth 2.0授权码流程——没有静态API密钥。Canister不会自行生成令牌;用户需在链下完成OAuth流程,并在调用时传入生成的Bearer访问令牌
令牌有效期:默认1小时(Google的访问令牌)。过期后API会返回HTTP 401。刷新令牌也必须在链下交换——canister无法调用Google的令牌端点(这会在链上暴露客户端密钥)。需返回
#Err("auth_expired")
结果,并请求调用方重新认证。
messages.send
所需的OAuth 2.0权限范围:
https://www.googleapis.com/auth/gmail.send
。如需读取权限,需添加:
https://www.googleapis.com/auth/gmail.readonly

Usage

使用示例

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
    is_replicated = ?false
    (non-replicated) for sends
    (
    gmail_users_messages_send
    ,
    gmail_users_drafts_send
    ). These outcalls are non-idempotent and Gmail's response is non-deterministic (unique message
    id
    , per-request
    Date
    header). In replicated mode (
    null
    ) 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 (
    gmail_users_messages_list
    ,
    gmail_users_messages_get
    ,
    gmail_users_getProfile
    ) also use
    ?false
    (one node, ~13× cheaper).
  • The
    $.xgafv
    parameter (version discriminator) should always be
    #_1_
    for Gmail API v1 calls. The
    alt
    parameter should always be
    #json
    .
  • All optional string parameters (
    callback
    ,
    fields
    ,
    key
    ,
    oauthToken
    ,
    quotaUser
    ,
    uploadProtocol
    ,
    uploadType
    ) accept
    ""
    to omit them;
    prettyPrint
    can be
    false
    .
  • userId
    =
    "me"
    refers to the authenticated user. Explicit email addresses also work but require the
    https://mail.google.com/
    scope.
  • Messages must be in RFC 2822 format, passed as a plain
    Blob
    in the
    raw
    field (e.g.
    ?Text.encodeUtf8(mime)
    ). The client base64-encodes
    raw
    for the API — do not base64-encode it yourself (that double-encodes and Gmail rejects it). The
    Message.payload
    /
    MessagePart
    fields are for parsed read responses — don't try to build them for sending.
  • 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
    #Err("auth_expired")
    so the caller can re-authenticate off-chain and retry with a fresh token.
  • Draft.message
    holds a
    Message
    ;
    gmail_users_drafts_create
    builds a draft server-side. Use
    gmail_users_drafts_send
    to send a draft by its
    id
    .
  • max_response_bytes
    : 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).
  • Cycle budget:
    defaultConfig.cycles = 30_000_000_000
    (30B). On the IC, outbound HTTPS calls cost ~10–15B cycles for a typical send. Adjust if you see
    InsufficientCycles
    errors.
  • 发送操作需设置
    is_replicated = ?false
    (非复制模式)
    gmail_users_messages_send
    gmail_users_drafts_send
    )。这些出站调用是非幂等的,且Gmail的响应是非确定性的(每个请求有唯一的消息
    id
    Date
    头)。在复制模式
    null
    )下,每个子网副本都会发起请求——因此邮件会每个副本发送一次(重复发送),且不同的响应会导致IC共识失败(“无法达成共识。副本响应不一致”)。非复制模式下仅由单个节点执行一次发送操作。读取操作(
    gmail_users_messages_list
    gmail_users_messages_get
    gmail_users_getProfile
    )也建议使用
    ?false
    (单个节点执行,成本约低13倍)。
  • $.xgafv
    参数(版本标识符)在Gmail API v1调用中应始终设置为
    #_1_
    alt
    参数应始终设置为
    #json
  • 所有可选字符串参数(
    callback
    fields
    key
    oauthToken
    quotaUser
    uploadProtocol
    uploadType
    )可传入
    ""
    来省略;
    prettyPrint
    可设为
    false
  • userId
    =
    "me"
    代表已认证用户。也可使用明确的邮箱地址,但需要
    https://mail.google.com/
    权限范围。
  • 消息必须采用RFC 2822格式,以**纯
    Blob
    **形式传入
    raw
    字段(例如
    ?Text.encodeUtf8(mime)
    )。客户端会为API对
    raw
    进行base64编码——请勿自行进行base64编码(这会导致双重编码,Gmail会拒绝该消息)。
    Message.payload
    /
    MessagePart
    字段用于解析读取响应——请勿尝试构建这些字段用于发送。
  • 当超出配额限制时,Google会返回HTTP 429。需将错误反馈给调用方;切勿在canister内部静默重试——重试发送可能会导致重复投递。
  • 访问令牌1小时后过期。遇到401错误时,返回
    #Err("auth_expired")
    ,以便调用方在链下重新认证并使用新令牌重试。
  • Draft.message
    包含一个
    Message
    gmail_users_drafts_create
    用于在服务器端创建草稿。使用
    gmail_users_drafts_send
    通过草稿
    id
    发送草稿。
  • max_response_bytes
    :读取Gmail消息可能会返回较大的数据。500 KB足以覆盖典型消息;对于包含大负载的消息,可将其提升至2 MB(Gmail API的最大响应受API限制,但需为周期预算保守设置)。
  • 周期预算:
    defaultConfig.cycles = 30_000_000_000
    (300亿)。在IC上,出站HTTPS调用的典型发送成本约为100-150亿周期。如果遇到
    InsufficientCycles
    错误,请调整该值。