connector-googlemail

Use the `googlemail-client` mops package whenever the user asks the canister to send email, compose a draft, list or read Gmail messages, or fetch the authenticated user's Gmail profile. The package wraps the Gmail REST API v1 at `https://gmail.googleapis.com` via outbound HTTPS calls.

npx skills add https://github.com/caffeinelabs/skills --skill connector-googlemail

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.

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".

How Gmail authentication works (read before wiring)

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.

Usage

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.

Больше skills от caffeinelabs

extension-stripe
caffeinelabs
Поддержка платежей на основе Stripe, поддерживающая кредитные и дебетовые карты
extension-object-storage
caffeinelabs
Общее хранилище файлов/объектов, например для изображений, видео, файлов, документов и других объемных данных. Идеально подходит для галерей изображений, видеогалерей и других систем управления файлами или объектами. Поддерживает большие файлы, превышающие лимит IC, с доступом по HTTP-URL, кэшируемым браузером.
developmentmedia
extension-openai
caffeinelabs
MANDATORY recipe for every Caffeine build that calls OpenAI (ChatGPT, GPT-4o, an LLM, a chatbot, embeddings). The ONLY supported path is the `openai-client` mops package with a canister-side API-key bearer. Hand-rolling `ic.http_request` to `api.openai.com/v1/...` is a FORBIDDEN anti-pattern — it leaks the bearer across replicated outcalls (security + 13× billing impact), bypasses the typed request/response bindings, and forces hand-rolled JSON on a language with poor JSON support. Load this...
developmentapisecurity
extension-http-outcalls
caffeinelabs
HTTP outcalls performed by the backend canister (not in the frontend).
developmentapi
extension-querying-oql
caffeinelabs
Quick reference for the Caffeine Data Intelligence agent to query an OQL-exposing canister (schema() + execute()) through the `icp` CLI against the project's `backend` canister: read the schema, form JSON queries (filter / order / paginate / aggregate / dotted-path edges), and parse the Candid result rows.
developmentdatabaseapi
extension-core-infrastructure
caffeinelabs
Базовая инфраструктура, предоставляющая конфигурацию серверного подключения, клиент хранилища и точку входа React-приложения.
developmentapidevops
extension-posting-to-x
caffeinelabs
MANDATORY recipe for every Caffeine build that posts to X (Twitter). The ONLY supported path is the `x-client` mops package with OAuth 2.0 PKCE. Hand-rolling `ic.http_request` or `icBooking.http_request` calls to `api.x.com/2/tweets`, `api.x.com/2/oauth2/token`, or any other X endpoint is a FORBIDDEN anti-pattern — it bypasses bearer auth, replication-cost safeguards, and `x-client`'s null-field handling. Load this skill whenever the user, spec, or any prior task mentions tweeting,...
developmentapi
extension-qr-code
caffeinelabs
QR code scanner using the camera.
productivitymediaimage