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.

Mehr Skills von caffeinelabs

extension-stripe
caffeinelabs
Payment support based on Stripe, supporting credit cards and debit cards
extension-object-storage
caffeinelabs
General file/object storage, such as for images, videos, files, documents and other bulk data. Perfect fit for image galleries, video galleries, and other file or object management. Supports large files beyond IC limit, with browser-cached HTTP URL access.
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
Core infrastructure providing backend connection configuration, storage client, and React app entry point.
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