raw field demands happen on the way out, and the MIME tree it returns
never reaches your model.
Authenticating
This pack takes a Google OAuth bearer token, andconfigure() is optional when $GOOGLE_ACCESS_TOKEN is set. Which credential provider you hand it depends on whose account the calls run as.
A token you hold
For a script, or a notebook.EnvTokenProvider re-reads the variable on every call, so a token rotated beside the process is picked up without a restart; StaticTokenProvider takes one you already hold as a string. Neither renews anything, so the calls stop when the token expires.
One account, refreshed
For an agent or a server acting as you.OAuth2Client turns a client registration and a stored refresh token into an access token, and renews it before it lapses.
Many end users
For a product whose users each connect their own account.SubjectProvider builds one credential per user through a factory you write, and use_subject names the user a call acts for. Your users’ accounts is the consent route inside your app; serving many users is the per-subject cache and its eviction.
The client
oauth_tool_factory is the whole client: a thin wrapper over httpx that attaches your token and these endpoint constants to each request. google-api-python-client and google-auth do not enter your dependency tree.credentials is whichever of the three you built in Authenticating. A pack takes it through configure(); a client you build takes the same object as credential_provider, and has no configure() of its own.
Paging through a list
A cursor belongs to the tool that returns it, so it is declared on that tool’s builder call:messages_list, threads_list and drafts_list. The other 20 take no cursor.Tools
Each is aTool, called with
ainvoke as in the snippet above. The name
links to its parameters, its response and what it costs.
What crosses the boundary
On the way out
messages_send, drafts_create and drafts_update take an
EmailContent (to, subject,
body, mimeType, cc, bcc), and
Format("rfc822_base64") turns it into the
base64url RFC-822 blob Gmail wants in raw. Transforms is
how the marker works, and the built-in list
is what else it could have said.
On the way back
The return direction is aMode declaration.
Message.payload, the whole MIME tree with attachment bytes included, is marked
response_only, so it is absent from the type the model fills in and cannot be
constructed by anything in the pipeline. messages_send shows four visible
fields against eight withheld; print it with
format_egress_map, which
egress control reads line by line.
Trimming a message
Six tools go further and run aResponseHandler:
extract_thread_text on threads_get, threads_modify, threads_trash and
threads_untrash, extract_message_text on messages_get, and
extract_draft_text on drafts_get. All three project a message the same way. They walk the MIME part tree, base64-decode text/plain
and text/calendar, convert text/html to text, and keep attachment metadata
without the bytes. All three are exported, so a tool of your own can hand
Gmail’s MIME tree to the same walk:
format a read asks for decides which representation arrives, and the
handler follows it. full and metadata fill payload; raw fills raw
instead, and gets parsed to the same shape; minimal fills neither, and comes
back with no bodyText key rather than an empty one.
The other 17 tools return Gmail’s payload as it arrived.
Gotchas
Gmail's schema fields are camelCase; the other Google packs' are not
Gmail's schema fields are camelCase; the other Google packs' are not
userId, maxResults and includeSpamTrash here, but
calendar_id and max_results on Google Calendar. Both
reach Google as camelCase — the key case cascade
converts Calendar’s, and Gmail’s are already there — but the names a model
sees differ between the two packs, and a prompt that hard-codes one will not
transfer to the other.Walking the pages of messages_list
Walking the pages of messages_list
next_page_args reads
nextPageToken off the page you just got and returns the arguments for the
next one, or None when Gmail stops sending a token:userId is always "me"
userId is always "me"
userId, and on an ordinary OAuth token the value is
"me" — Gmail’s special value for whichever mailbox the token belongs to.
It is a parameter of the API rather than of the pack, which is why it is on
the schemas the model fills in rather than hidden behind configure().Label operations take ids, not names
Label operations take ids, not names
messages_modify and messages_batch_modify take add_label_ids and
remove_label_ids. System labels are their own ids (INBOX, UNREAD,
SPAM); a user label’s id is the opaque Label_5 string that labels_list
returns, not its display name. Resolve the name first.labels_update replaces, labels_patch does not
labels_update replaces, labels_patch does not
labels_update is a PUT, so the label becomes exactly what you
send and name is required. labels_patch keeps every field you omit, so
it requires nothing: recolouring a label is a color and no name. The other
six fields on a Label are Gmail’s own, and neither endpoint offers them.A colour is two fields or none
A colour is two fields or none
textColor and backgroundColor as both required to set a
label’s colour, and answers a half-set one with a 400 that reads like any
other bad request. Color carries that as a validator, so the model is told
which field is missing before anything is sent. Both take hex strings from a
fixed palette, listed on each field.threads_delete is the one tool that needs a wider scope
threads_delete is the one tool that needs a wider scope
https://www.googleapis.com/auth/gmail.modify. Google requires
https://mail.google.com/, full mailbox access, for threads_delete
alone, because the delete cannot be undone. The pack exports it as
gmail.FULL_MAILBOX_SCOPE. It is declared on that tool rather than on the
pack, so
scopes_for adds it to a consent screen
only when you hand that tool out. Drop threads_delete from your tool list
and your users are asked for gmail.modify and nothing more.Trash is reversible, delete is not
Trash is reversible, delete is not
threads_trash moves a conversation and every message in it to TRASH,
and threads_untrash brings it back. threads_delete removes the thread
and its messages outright, and Google’s own reference says to prefer trash.
The same split exists for drafts, except there is no untrash for a draft:
drafts_delete is permanent and has no reversible counterpart.threads_modify labels every message in the conversation
threads_modify labels every message in the conversation
add_label_ids and remove_label_ids as
messages_modify and applies them thread-wide, so marking a conversation
read is one call rather than one per message. Gmail returns the whole
thread back, which is why the trimming handler runs on it.System labels cannot be edited or deleted
System labels cannot be edited or deleted
INBOX, SENT, DRAFT, SPAM and the rest come back from labels_list
with type: "system", and labels_update, labels_patch and
labels_delete all refuse them. Deleting a user label is permanent, and it
is removed from every message and thread it was on.messages_list returns ids only
messages_list returns ids only
{"id": ..., "threadId": ...} per message and
nothing else: no subject, no sender, no snippet. Reading content is a second
call, messages_get per message or threads_get for the whole
conversation. threads_get costs 40 quota units against 20 for a single
messages_get, so a conversation of three messages or more is cheaper read
as a thread.A draft's id is not its message's id
A draft's id is not its message's id
drafts_list and drafts_get return {"id": ..., "message": {"id": ...}},
and the two are different strings. drafts_update, drafts_send and
drafts_delete all take the draft id, the outer one. The message id belongs
to messages_get and messages_modify.drafts_update replaces, and drafts_delete does not trash
drafts_update replaces, and drafts_delete does not trash
drafts_update writes a whole message over the saved one, so send the draft
you want to end up with rather than the part that changed. drafts_delete
removes the draft permanently: it does not go to TRASH, and there is no
untrash for it.Related
- Google — consent screen, scopes, refresh
- Transforms — what
Format("rfc822_base64")does - Egress control — the
messages_sendmap in full