diff --git a/shortcuts/mail/mail_auth_types_test.go b/shortcuts/mail/mail_auth_types_test.go
new file mode 100644
index 000000000..9112581c6
--- /dev/null
+++ b/shortcuts/mail/mail_auth_types_test.go
@@ -0,0 +1,74 @@
+// Copyright (c) 2026 Lark Technologies Pte. Ltd.
+// SPDX-License-Identifier: MIT
+
+package mail
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/larksuite/cli/internal/cmdutil"
+ "github.com/larksuite/cli/internal/core"
+ "github.com/larksuite/cli/shortcuts/common"
+ "github.com/spf13/cobra"
+)
+
+// TestUserMailboxShortcutsRequireUserIdentity verifies that shortcuts backed
+// exclusively by user_mailbox.* APIs reject bot identity before making any
+// API call. The error must contain "not supported" so the caller knows the
+// issue is identity, not a bad parameter.
+func TestUserMailboxShortcutsRequireUserIdentity(t *testing.T) {
+ tests := []struct {
+ shortcut common.Shortcut
+ args []string // minimum args to pass cobra's required-flag check
+ }{
+ {
+ shortcut: MailTriage,
+ args: []string{"+triage", "--as", "bot"},
+ },
+ {
+ shortcut: MailMessages,
+ args: []string{"+messages", "--as", "bot", "--message-ids", "dummy"},
+ },
+ {
+ shortcut: MailTemplateCreate,
+ args: []string{"+template-create", "--as", "bot", "--name", "dummy"},
+ },
+ {
+ shortcut: MailThread,
+ args: []string{"+thread", "--as", "bot", "--thread-id", "dummy"},
+ },
+ {
+ shortcut: MailTemplateUpdate,
+ args: []string{"+template-update", "--as", "bot"},
+ },
+ {
+ shortcut: MailMessage,
+ args: []string{"+message", "--as", "bot", "--message-id", "dummy"},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.shortcut.Command, func(t *testing.T) {
+ f, _, _, _ := cmdutil.TestFactory(t, &core.CliConfig{
+ AppID: "test-app",
+ AppSecret: "test-secret",
+ Brand: core.BrandFeishu,
+ })
+
+ parent := &cobra.Command{Use: "mail"}
+ tt.shortcut.Mount(parent, f)
+ parent.SetArgs(tt.args)
+ parent.SilenceErrors = true
+ parent.SilenceUsage = true
+
+ err := parent.Execute()
+ if err == nil {
+ t.Fatal("expected error for bot identity, got nil")
+ }
+ if !strings.Contains(err.Error(), "not supported") {
+ t.Errorf("expected 'not supported' in error, got: %v", err)
+ }
+ })
+ }
+}
diff --git a/shortcuts/mail/mail_message.go b/shortcuts/mail/mail_message.go
index 86fabb51e..b18ec2de5 100644
--- a/shortcuts/mail/mail_message.go
+++ b/shortcuts/mail/mail_message.go
@@ -18,7 +18,7 @@ var MailMessage = common.Shortcut{
Description: "Use when reading full content for a single email by message ID. Returns normalized body content plus attachments metadata, including inline images.",
Risk: "read",
Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"},
- AuthTypes: []string{"user", "bot"},
+ AuthTypes: []string{"user"},
HasFormat: true,
Flags: []common.Flag{
{Name: "mailbox", Default: "me", Desc: "email address (default: me)"},
diff --git a/shortcuts/mail/mail_messages.go b/shortcuts/mail/mail_messages.go
index 444aa105f..c9532feb2 100644
--- a/shortcuts/mail/mail_messages.go
+++ b/shortcuts/mail/mail_messages.go
@@ -26,7 +26,7 @@ var MailMessages = common.Shortcut{
Description: "Use when reading full content for multiple emails by message ID. Prefer this shortcut over calling raw mail user_mailbox.messages batch_get directly, because it base64url-decodes body fields and returns normalized per-message output that is easier to consume.",
Risk: "read",
Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"},
- AuthTypes: []string{"user", "bot"},
+ AuthTypes: []string{"user"},
HasFormat: true,
Flags: []common.Flag{
{Name: "mailbox", Default: "me", Desc: "email address (default: me)"},
diff --git a/shortcuts/mail/mail_template_create.go b/shortcuts/mail/mail_template_create.go
index 29a8978b4..8f18329b2 100644
--- a/shortcuts/mail/mail_template_create.go
+++ b/shortcuts/mail/mail_template_create.go
@@ -19,7 +19,7 @@ var MailTemplateCreate = common.Shortcut{
Description: "Create a personal mail template. Scans HTML
local paths (reusing draft inline-image detection), uploads inline images and non-inline attachments to Drive, rewrites HTML to cid: references, and POSTs a Template payload to mail.user_mailbox.templates.create.",
Risk: "write",
Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox:readonly"},
- AuthTypes: []string{"user", "bot"},
+ AuthTypes: []string{"user"},
HasFormat: true,
Flags: []common.Flag{
{Name: "mailbox", Desc: "Mailbox email address that owns the template (default: me)."},
diff --git a/shortcuts/mail/mail_template_update.go b/shortcuts/mail/mail_template_update.go
index d5f68ce2c..dd54bce7c 100644
--- a/shortcuts/mail/mail_template_update.go
+++ b/shortcuts/mail/mail_template_update.go
@@ -20,7 +20,7 @@ var MailTemplateUpdate = common.Shortcut{
Description: "Update an existing mail template. Supports --inspect (read-only projection), --print-patch-template (prints a JSON skeleton for --patch-file), and flat flags (--set-subject / --set-name / etc). Internally it GETs the template, applies the patch, rewrites
local paths to cid: refs, and PUTs a full-replace update (no optimistic locking: last-write-wins).",
Risk: "write",
Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox:readonly"},
- AuthTypes: []string{"user", "bot"},
+ AuthTypes: []string{"user"},
HasFormat: true,
Flags: []common.Flag{
{Name: "mailbox", Desc: "Mailbox email address that owns the template (default: me)."},
diff --git a/shortcuts/mail/mail_thread.go b/shortcuts/mail/mail_thread.go
index 67d3fbebd..56c2d4ff6 100644
--- a/shortcuts/mail/mail_thread.go
+++ b/shortcuts/mail/mail_thread.go
@@ -49,7 +49,7 @@ var MailThread = common.Shortcut{
Description: "Use when querying a full mail conversation/thread by thread ID. Returns all messages in chronological order, including replies and drafts, with body content and attachments metadata, including inline images.",
Risk: "read",
Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"},
- AuthTypes: []string{"user", "bot"},
+ AuthTypes: []string{"user"},
HasFormat: true,
Flags: []common.Flag{
{Name: "mailbox", Default: "me", Desc: "email address (default: me)"},
diff --git a/shortcuts/mail/mail_triage.go b/shortcuts/mail/mail_triage.go
index 1274c5f11..b056633c6 100644
--- a/shortcuts/mail/mail_triage.go
+++ b/shortcuts/mail/mail_triage.go
@@ -52,7 +52,7 @@ var MailTriage = common.Shortcut{
Description: `List mail summaries (date/from/subject/message_id). Use --query for full-text search, --filter for exact-match conditions.`,
Risk: "read",
Scopes: []string{"mail:user_mailbox.message:readonly", "mail:user_mailbox.message.address:read", "mail:user_mailbox.message.subject:read", "mail:user_mailbox.message.body:read"},
- AuthTypes: []string{"user", "bot"},
+ AuthTypes: []string{"user"},
Flags: []common.Flag{
{Name: "format", Default: "table", Desc: "output format: table | json | data (json/data output object with pagination fields)"},
{Name: "max", Type: "int", Default: "20", Desc: "maximum number of messages to fetch (1-400; auto-paginates internally)"},