Skip to content

feat(gmail): Each user signs in, conversation, label and trash tools, register download_attachment (1.0.0) - #4180

Merged
crazywoola merged 2 commits into
langgenius:mainfrom
ForeignKeyCN:feat/gmail-per-user-signin
Nov 2, 2026
Merged

crazywoola merged 2 commits into
langgenius:mainfrom
ForeignKeyCN:feat/gmail-per-user-signin

Conversation

@ForeignKeyCN

Copy link
Copy Markdown
Contributor

Summary

Gmail 1.0.0 adds Each user signs in next to the existing OAuth credential, adds tools for conversations, labels, trash and drafts, and registers a tool that was never visible. It reuses the Google sign-in module from Google Calendar (#4173) and follows the same per-user design as the Microsoft and Atlassian plugins (#4163–#4171).

Fix: download_attachment was never registered. The tool and its YAML were added in #2573, but provider/dify-gmail.yaml never listed it, so it didn't appear in Dify. It is listed now.

Each user signs in

A new API-key style credential holds:

  • Mode: Each user signs in.
  • Client ID and Client Secret of the Google Web-application client.
  • Sign-in Callback URL: the plugin endpoint GET /gmail/callback.

The flow is the same as Google Calendar's:

  • Sign-in link: the first time a chat user needs Gmail, the tool returns a Google sign-in link. It uses authorization code + PKCE, access_type=offline and prompt=consent. The scopes are those of the OAuth credential plus openid email.
  • Token handling: the endpoint only stores the one-time code; the tool redeems it with the client secret and the PKCE verifier. Per-user tokens are Fernet-encrypted in plugin storage, keyed by a hash of client_id | Dify user ID, and never appear in tool output.
  • Account name: after sign-in, the agent reports which Google account was used.
  • Unticked permissions: when a Gmail permission is unticked on the consent screen, the user is asked to sign in again.
  • Expired or revoked access: invalid_grant or a 401 starts a new sign-in.
  • Forwarded links: the callback page warns people who did not start the sign-in and lets them cancel it.
  • Sign-out: gmail_sign_out forgets the user's sign-in.

The 9 original tools get the token through a small connect() helper, so they work in both modes. Their names, parameters and output are otherwise unchanged, and existing OAuth credentials keep working.

New tools (prefixed gmail_: the new Agent rejects duplicate tool names across plugins)

Area Tool
Reply gmail_reply:
• in the message's conversation (threadId, In-Reply-To, References, Re: subject)
• reply_all copies the other recipients but never you
• replying to your own message answers its recipients
• optional HTML body
Conversations gmail_get_thread: every message with sender, date and text; HTML-only messages are reduced to text. gmail_trash_thread: trash or restore a whole conversation.
Messages gmail_trash_message (trash or restore), gmail_mark_read (read or unread)
Labels gmail_modify_labels:
• add or remove labels by name or ID on a message or a whole conversation
• system labels (INBOX to archive, STARRED, IMPORTANT, …)
• optional create_missing
gmail_list_labels, gmail_create_label (nested with /), gmail_delete_label (system labels refused)
Drafts gmail_get_draft, gmail_delete_draft
  • Every new tool returns typed output variables (success / error plus its fields).
  • IDs are encoded as one path segment each.
  • The original tool names (get_message, send_message, …) are kept to avoid breaking workflows. They clash with Outlook in one new-style Agent; the README documents this.

Release Notes

  • Each user signs in: chat agents can let every user work in their own mailbox.
  • New tools:
    • reply in a conversation;
    • read or trash whole conversations;
    • mark read or unread;
    • add or remove labels;
    • manage labels;
    • read or delete drafts.
  • Fix: the existing "Download Attachment" tool now appears in Dify.

Change Type

  • Documentation / non-plugin change
  • Non-LLM plugin (tools, extensions, datasource, etc.)
  • LLM plugin

Screenshots / Videos

The README adds two chat screenshots:

  • the agent asking the user to sign in, then summarizing their mailbox;
  • replying, labelling the conversation and marking it read as the signed-in user.

Testing

  • Unit tests: 58 passed (pytest tests). They cover:
    • Sign-in flow: PKCE link with the Gmail scopes, callback, redeem, cache, refresh keeping the refresh token, invalid_grant or 401 starting a new link, an unticked permission, per-user isolation, secret rotation, the cancel and error pages, sign-out.
    • gmail_reply: threading headers, reply-all without yourself, replying to your own message, the HTML part.
    • Labels: by name, system labels, create-missing and unknown labels; refusing to delete a system label.
    • Conversations and drafts: plain and HTML-only bodies, reading a draft.
    • Other tools: trash and restore, read and unread, path encoding.
    • Original tools: work in both modes.
    • Plugin: every tool's output variables, the manifest and endpoint, provider validation.
  • Self-hosted Dify (Community), Each user signs in, real Gmail account, in an agent chat:
    1. Google sign-in, then the agent summarized the 3 most recent emails (search_messages).
    2. Sent an email to the account itself (send_message).
    3. Replied "got it" in that conversation (gmail_reply), labelled the conversation "Dify" (gmail_modify_labels, which created the label) and marked it read (gmail_mark_read).
  • Not run against Gmail: trash/restore, label deletion, gmail_get_thread and the draft tools (unit tests only).

Checklist

  • I have read the contribution guidelines
  • Version bumped: 0.2.8 → 1.0.0

The agent retypes the sign-in link in its reply, and a long random state
does not always survive that: in a real chat one character changed case,
so the callback endpoint could not recognize the sign-in.

The tool now returns <sign-in endpoint>?start=<12 lowercase characters
without look-alikes>, and the endpoint redirects to the sign-in page it
stored. The state and PKCE values go to the provider and back without
passing through the agent.
@crazywoola
crazywoola deployed to tools/gmail November 2, 2026 16:18 — with GitHub Actions Active
@crazywoola
crazywoola merged commit a831539 into langgenius:main Nov 2, 2026
4 checks passed

This branch was successfully deployed

1 active deployment
tools/gmail — 7043561a Deployed Nov 2, 2026 by crazywoola via test (tools/gmail) #2957
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants