@littlebearapps/outlook-assistant 3.11.2 → 3.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +20 -0
- package/README.md +51 -28
- package/advanced/index.js +319 -46
- package/auth/device-code.js +100 -3
- package/auth/token-storage.js +44 -2
- package/auth/tools.js +196 -14
- package/calendar/attendees.js +101 -0
- package/calendar/cancel.js +5 -4
- package/calendar/create.js +15 -4
- package/calendar/decline.js +10 -5
- package/calendar/index.js +51 -10
- package/calendar/list.js +146 -3
- package/calendar/update.js +65 -33
- package/categories/index.js +17 -3
- package/config.js +103 -17
- package/contacts/index.js +2 -1
- package/email/attachments.js +19 -37
- package/email/conversations.js +180 -91
- package/email/delta.js +123 -13
- package/email/draft.js +66 -9
- package/email/export.js +113 -77
- package/email/folder-utils.js +29 -129
- package/email/headers.js +5 -1
- package/email/index.js +76 -19
- package/email/list.js +8 -1
- package/email/mark-as-read.js +3 -1
- package/email/mime.js +4 -1
- package/email/read.js +5 -1
- package/email/search.js +23 -9
- package/folder/create.js +11 -4
- package/folder/delete.js +9 -1
- package/folder/index.js +11 -1
- package/folder/list.js +61 -27
- package/folder/move.js +32 -7
- package/folder/resolve.js +65 -25
- package/folder/stats.js +11 -5
- package/index.js +9 -1
- package/llms-install.md +28 -9
- package/llms.txt +13 -9
- package/package.json +3 -3
- package/rules/index.js +3 -3
- package/rules/rule-builder.js +61 -16
- package/utils/datetime.js +170 -0
- package/utils/graph-api.js +390 -211
- package/utils/mailbox.js +77 -0
- package/utils/mock-data.js +3 -0
- package/utils/odata-helpers.js +24 -0
- package/utils/safe-write.js +151 -0
- package/calendar/accept.js +0 -72
package/email/headers.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
const { callGraphAPI } = require('../utils/graph-api');
|
|
8
8
|
const { ensureAuthenticated } = require('../auth');
|
|
9
|
+
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Important headers to highlight (in order of relevance)
|
|
@@ -158,6 +159,9 @@ async function handleGetEmailHeaders(args) {
|
|
|
158
159
|
const groupByType = args.groupByType || false;
|
|
159
160
|
const importantOnly = args.importantOnly || false;
|
|
160
161
|
const raw = args.raw || false;
|
|
162
|
+
// Shared-mailbox message IDs aren't resolvable under /me — route to the
|
|
163
|
+
// owning mailbox when a sharedMailbox/email is supplied.
|
|
164
|
+
const prefix = buildMailboxPrefix(args.sharedMailbox || args.email || null);
|
|
161
165
|
|
|
162
166
|
if (!emailId) {
|
|
163
167
|
return {
|
|
@@ -187,7 +191,7 @@ async function handleGetEmailHeaders(args) {
|
|
|
187
191
|
'sentDateTime',
|
|
188
192
|
].join(',');
|
|
189
193
|
|
|
190
|
-
const endpoint =
|
|
194
|
+
const endpoint = `${prefix}/messages/${emailId}`;
|
|
191
195
|
const queryParams = {
|
|
192
196
|
$select: selectFields,
|
|
193
197
|
};
|
package/email/index.js
CHANGED
|
@@ -33,7 +33,7 @@ const emailTools = [
|
|
|
33
33
|
{
|
|
34
34
|
name: 'search-emails',
|
|
35
35
|
description:
|
|
36
|
-
'Search, list, delta-sync, or thread-group emails — six modes selected by parameters (read-only). With no params: lists recent emails in `folder` (default `inbox`). With `query`/`from`/`to`/`subject`/date filters: full search (combines via OData filter). With `searchExpression` (deprecated alias `kqlQuery`): a raw Microsoft Graph `$search` expression for advanced server-side search. With `deltaMode: true`: returns current state plus a `deltaToken`; pass the token back on the next call for incremental changes only — ideal for inbox monitoring. With `groupByConversation: true`: returns conversation threads. With `conversationId`: returns all messages in a single thread. With `internetMessageId`: looks up a message by its RFC Message-ID header. Personal Outlook.com accounts have limited `$search` support — this tool falls back through OData filters / boolean filters / recent listing automatically, but structured filters (`from`/`subject`/`receivedAfter`/`hasAttachments`/`unreadOnly`) return cleaner results. Returns paged messages with id/subject/from/receivedDateTime/preview by default; use `outputVerbosity` to expand.',
|
|
36
|
+
'Search, list, delta-sync, or thread-group emails — six modes selected by parameters (read-only). With no params: lists recent emails in `folder` (default `inbox`). With `query`/`from`/`to`/`subject`/date filters: full search (combines via OData filter). With `searchExpression` (deprecated alias `kqlQuery`): a raw Microsoft Graph `$search` expression for advanced server-side search. With `deltaMode: true`: returns current state plus a `deltaToken`; pass the token back on the next call for incremental changes only — ideal for inbox monitoring. With `groupByConversation: true`: returns conversation threads. With `conversationId`: returns all messages in a single thread. With `internetMessageId`: looks up a message by its RFC Message-ID header. Set `sharedMailbox` (or alias `email`) to search a shared/delegated mailbox instead of the signed-in account — works with custom folders and nested folder paths. Personal Outlook.com accounts have limited `$search` support — this tool falls back through OData filters / boolean filters / recent listing automatically, but structured filters (`from`/`subject`/`receivedAfter`/`hasAttachments`/`unreadOnly`) return cleaner results. Returns paged messages with id/subject/from/receivedDateTime/preview by default; use `outputVerbosity` to expand.',
|
|
37
37
|
annotations: {
|
|
38
38
|
title: 'Search Emails',
|
|
39
39
|
readOnlyHint: true,
|
|
@@ -48,22 +48,22 @@ const emailTools = [
|
|
|
48
48
|
deltaMode: {
|
|
49
49
|
type: 'boolean',
|
|
50
50
|
description:
|
|
51
|
-
'Enable delta sync mode. Returns only changes since last sync. Use deltaToken for subsequent calls.',
|
|
51
|
+
'Enable delta sync mode. Returns only changes since last sync. Use deltaToken for subsequent calls; an initial sync larger than `maxResults` arrives over several pages, each returning a continuation token to pass back until a delta token is returned. Honors `sharedMailbox`/`email` (and custom `folder` paths) to sync within a shared/delegated mailbox.',
|
|
52
52
|
},
|
|
53
53
|
internetMessageId: {
|
|
54
54
|
type: 'string',
|
|
55
55
|
description:
|
|
56
|
-
'Look up email by Message-ID header (e.g. <abc123@example.com>). For threading/deduplication.',
|
|
56
|
+
'Look up email by Message-ID header (e.g. <abc123@example.com>). For threading/deduplication. Honors `sharedMailbox`/`email` to look up within a shared/delegated mailbox.',
|
|
57
57
|
},
|
|
58
58
|
conversationId: {
|
|
59
59
|
type: 'string',
|
|
60
60
|
description:
|
|
61
|
-
'Get
|
|
61
|
+
'Get the messages in a conversation thread by conversationId, oldest first (up to 100; a longer thread is marked truncated — use `export target=conversation` for up to 1000). Honors `sharedMailbox`/`email` to thread within a shared/delegated mailbox.',
|
|
62
62
|
},
|
|
63
63
|
groupByConversation: {
|
|
64
64
|
type: 'boolean',
|
|
65
65
|
description:
|
|
66
|
-
'List conversations (threads) grouped by conversationId instead of individual emails.',
|
|
66
|
+
'List conversations (threads) grouped by conversationId instead of individual emails. Honors `sharedMailbox`/`email` (and custom `folder` paths) to group within a shared/delegated mailbox.',
|
|
67
67
|
},
|
|
68
68
|
// Search/list params
|
|
69
69
|
query: {
|
|
@@ -74,7 +74,7 @@ const emailTools = [
|
|
|
74
74
|
searchExpression: {
|
|
75
75
|
type: 'string',
|
|
76
76
|
description:
|
|
77
|
-
'Raw Microsoft Graph `$search` expression for advanced server-side search, e.g. `subject:"invoice"`, `from:github.com`, or `foo OR bar`. Quote your own phrases; a single bare token is auto-quoted. Pair with `searchAllFolders: true` for cross-folder search. Bypasses other search params. NOTE: personal Outlook.com accounts reject field-scoped `$search` outright; since v3.10.0 recognised `from:`/`to:`/`subject:` expressions are translated into the closest equivalent OData filters and retried automatically (a `subject:` term becomes a substring match, so it is close but not identical) (reported as strategy `raw-kql-translated`). Expressions that cannot be translated exactly — free text, `AND`/`OR`, unknown prefixes — are not retried, so use `query` for those there. RELEVANCE, NOT RECENCY: an untranslated expression is answered by Graph `$search` over the whole message including the body, ranked by relevance and not sorted by date, so top hits can look unrelated to a caller expecting a subject match. `query` is the more predictable choice for a term you expect in a subject line; `searchExpression` is the one that reaches body text.',
|
|
77
|
+
'Raw Microsoft Graph `$search` expression for advanced server-side search, e.g. `subject:"invoice"`, `from:github.com`, or `foo OR bar`. Quote your own phrases, escaping any `"` or `\\` inside them with a backslash; a single bare token is auto-quoted and escaped for you. Pair with `searchAllFolders: true` for cross-folder search. Bypasses other search params. NOTE: personal Outlook.com accounts reject field-scoped `$search` outright; since v3.10.0 recognised `from:`/`to:`/`subject:` expressions are translated into the closest equivalent OData filters and retried automatically (a `subject:` term becomes a substring match, so it is close but not identical) (reported as strategy `raw-kql-translated`). Expressions that cannot be translated exactly — free text, `AND`/`OR`, unknown prefixes — are not retried, so use `query` for those there. RELEVANCE, NOT RECENCY: an untranslated expression is answered by Graph `$search` over the whole message including the body, ranked by relevance and not sorted by date, so top hits can look unrelated to a caller expecting a subject match. `query` is the more predictable choice for a term you expect in a subject line; `searchExpression` is the one that reaches body text.',
|
|
78
78
|
},
|
|
79
79
|
kqlQuery: {
|
|
80
80
|
type: 'string',
|
|
@@ -83,7 +83,17 @@ const emailTools = [
|
|
|
83
83
|
},
|
|
84
84
|
folder: {
|
|
85
85
|
type: 'string',
|
|
86
|
-
description:
|
|
86
|
+
description:
|
|
87
|
+
"Email folder (default: 'inbox'). Accepts a well-known name, a custom/localized display name, or a nested path like `Inbox/Subfolder`.",
|
|
88
|
+
},
|
|
89
|
+
sharedMailbox: {
|
|
90
|
+
type: 'string',
|
|
91
|
+
description:
|
|
92
|
+
'Email address of a shared/delegated mailbox to search instead of the signed-in account. Combine with `folder` (incl. custom subfolders/paths) or `searchAllFolders`. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).',
|
|
93
|
+
},
|
|
94
|
+
email: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description: 'Alias for `sharedMailbox`.',
|
|
87
97
|
},
|
|
88
98
|
from: {
|
|
89
99
|
type: 'string',
|
|
@@ -132,12 +142,12 @@ const emailTools = [
|
|
|
132
142
|
deltaToken: {
|
|
133
143
|
type: 'string',
|
|
134
144
|
description:
|
|
135
|
-
'Token from previous delta call for incremental sync (deltaMode only)',
|
|
145
|
+
'Token from previous delta call for incremental sync (deltaMode only). The token is authoritative — it encodes its own mailbox and folder, so `folder`/`sharedMailbox` are ignored and a token from a different mailbox is rejected.',
|
|
136
146
|
},
|
|
137
147
|
maxResults: {
|
|
138
148
|
type: 'number',
|
|
139
149
|
description:
|
|
140
|
-
'
|
|
150
|
+
'Delta sync page size (deltaMode only): 1-200, default 100, sent to Graph as the `Prefer: odata.maxpagesize` header. It sizes each page, not the whole sync: while a page returns a continuation token, keep calling with that token until a delta token is returned, and pass the same `maxResults` on every page (an omitted value means 100).',
|
|
141
151
|
},
|
|
142
152
|
// Conversation params
|
|
143
153
|
includeHeaders: {
|
|
@@ -158,6 +168,7 @@ const emailTools = [
|
|
|
158
168
|
return handleSearchByMessageId({
|
|
159
169
|
messageId: args.internetMessageId,
|
|
160
170
|
outputVerbosity: args.outputVerbosity,
|
|
171
|
+
sharedMailbox: args.sharedMailbox || args.email || null,
|
|
161
172
|
});
|
|
162
173
|
}
|
|
163
174
|
if (args.conversationId) {
|
|
@@ -189,7 +200,7 @@ const emailTools = [
|
|
|
189
200
|
{
|
|
190
201
|
name: 'read-email',
|
|
191
202
|
description:
|
|
192
|
-
'Read a single email by id (read-only). Default: returns the full message body (HTML stripped to text by default), subject, from/to/cc, receivedDateTime, conversationId, attachments metadata, and webLink as Markdown. With `headersMode: true`: returns RFC-822 forensic headers instead (DKIM, SPF, DMARC, Received chain, Message-ID, Authentication-Results) — pair with `importantOnly: true` for the security-relevant subset, `groupByType: true` for category-bucketed view, or `raw: true` for JSON instead of Markdown. With `includeHeaders: true` (non-headers-mode): adds basic headers alongside body. Use `outputVerbosity` (minimal/standard/full) to control field count.',
|
|
203
|
+
'Read a single email by id (read-only). Default: returns the full message body (HTML stripped to text by default), subject, from/to/cc, receivedDateTime, conversationId, attachments metadata, and webLink as Markdown. With `headersMode: true`: returns RFC-822 forensic headers instead (DKIM, SPF, DMARC, Received chain, Message-ID, Authentication-Results) — pair with `importantOnly: true` for the security-relevant subset, `groupByType: true` for category-bucketed view, or `raw: true` for JSON instead of Markdown. With `includeHeaders: true` (non-headers-mode): adds basic headers alongside body. Use `outputVerbosity` (minimal/standard/full) to control field count. **If the id came from a shared/delegated mailbox (e.g. via `search-emails` or `access-shared-mailbox` with `sharedMailbox` set), you MUST pass the same `sharedMailbox` (or alias `email`) here** — message IDs are mailbox-scoped, and reading a shared-mailbox id without it fails with 404 ErrorInvalidMailboxItemId.',
|
|
193
204
|
annotations: {
|
|
194
205
|
title: 'Read Email',
|
|
195
206
|
readOnlyHint: true,
|
|
@@ -203,6 +214,15 @@ const emailTools = [
|
|
|
203
214
|
type: 'string',
|
|
204
215
|
description: 'ID of the email to read',
|
|
205
216
|
},
|
|
217
|
+
sharedMailbox: {
|
|
218
|
+
type: 'string',
|
|
219
|
+
description:
|
|
220
|
+
'Email address of the shared/delegated mailbox the id belongs to. Required when the id was obtained from a shared mailbox — message IDs are mailbox-scoped and reading without it returns 404 ErrorInvalidMailboxItemId. Requires delegate access + Mail.Read.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).',
|
|
221
|
+
},
|
|
222
|
+
email: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
description: 'Alias for `sharedMailbox`.',
|
|
225
|
+
},
|
|
206
226
|
headersMode: {
|
|
207
227
|
type: 'boolean',
|
|
208
228
|
description:
|
|
@@ -307,7 +327,7 @@ const emailTools = [
|
|
|
307
327
|
{
|
|
308
328
|
name: 'draft',
|
|
309
329
|
description:
|
|
310
|
-
'Full draft lifecycle for review-before-send workflows (destructive: covers `send` and `delete`). action=`create` saves a new draft in the Drafts folder and returns its id (use `dryRun: true` to preview without saving; `checkRecipients: true` runs mail-tips first). action=`update` patches an existing draft by `id` (only fields passed are changed). action=`send` dispatches an existing draft — shares the rate limit with `send-email`. action=`delete`
|
|
330
|
+
'Full draft lifecycle for review-before-send workflows (destructive: covers `send` and `delete`). action=`create` saves a new draft in the Drafts folder and returns its id (use `dryRun: true` to preview without saving; `checkRecipients: true` runs mail-tips first). action=`update` patches an existing draft by `id` (only fields passed are changed). action=`send` dispatches an existing draft — shares the rate limit with `send-email`. action=`delete` deletes a draft: it skips Deleted Items and goes to Recoverable Items (restorable for a limited time, depending on your account, via "Recover deleted items" in Outlook). update/send/delete refuse any `id` that is not an unsent draft (received or sent messages are never changed). action=`reply`/`reply-all` creates a reply draft from a message `id` (use `comment` to prepend text — mutually exclusive with `body`). action=`forward` creates a forward draft (requires `id` and `to`). Recipient allowlist applies to create/update/forward. Returns the draft object on create/update/reply/forward; status confirmation on send/delete.',
|
|
311
331
|
annotations: {
|
|
312
332
|
title: 'Draft Operations',
|
|
313
333
|
readOnlyHint: false,
|
|
@@ -334,7 +354,7 @@ const emailTools = [
|
|
|
334
354
|
id: {
|
|
335
355
|
type: 'string',
|
|
336
356
|
description:
|
|
337
|
-
'Draft or message ID. Required for update/send/delete/reply/reply-all/forward.',
|
|
357
|
+
'Draft or message ID. Required for update/send/delete/reply/reply-all/forward. update/send/delete need a draft ID; reply/reply-all/forward take any message ID.',
|
|
338
358
|
},
|
|
339
359
|
to: {
|
|
340
360
|
type: 'string',
|
|
@@ -386,7 +406,7 @@ const emailTools = [
|
|
|
386
406
|
{
|
|
387
407
|
name: 'update-email',
|
|
388
408
|
description:
|
|
389
|
-
'Update message state without modifying content (idempotent — safe to retry). action=`mark-read`/`mark-unread` toggles the `isRead` flag on a single message by `id`. action=`flag` sets a follow-up flag with optional `dueDateTime`/`startDateTime` (ISO 8601). action=`unflag` clears the flag. action=`complete` marks the flag as done. Flag/unflag/complete accept either `id` (single) or `ids` (batch array) — batch
|
|
409
|
+
'Update message state without modifying content (idempotent — safe to retry). action=`mark-read`/`mark-unread` toggles the `isRead` flag on a single message by `id`. action=`flag` sets a follow-up flag with optional `dueDateTime`/`startDateTime` (ISO 8601 with a time: a value with `Z` or a ±hh:mm offset is kept as that exact instant; a value without one is read in the configured default timezone (OUTLOOK_DEFAULT_TIMEZONE); date-only or unparseable values are refused before any change). With only `dueDateTime`, the start defaults to 09:00 on the due date in the default timezone, or to the due time if that is earlier. action=`unflag` clears the flag. action=`complete` marks the flag as done. Flag/unflag/complete accept either `id` (single) or `ids` (batch array) — messages in a batch are updated one at a time (one PATCH each, not Graph `$batch`). Pass `sharedMailbox` (or alias `email`) to update messages in a shared/delegated mailbox instead of the signed-in account (requires Mail.ReadWrite.Shared + delegate access). Returns status confirmation per message.',
|
|
390
410
|
annotations: {
|
|
391
411
|
title: 'Update Email',
|
|
392
412
|
readOnlyHint: false,
|
|
@@ -416,40 +436,59 @@ const emailTools = [
|
|
|
416
436
|
// Flag params
|
|
417
437
|
dueDateTime: {
|
|
418
438
|
type: 'string',
|
|
419
|
-
description:
|
|
439
|
+
description:
|
|
440
|
+
'Due date/time for follow-up (action=flag). ISO 8601 with a time: "2026-03-01T09:00:00Z" or "2026-03-01T09:00:00+10:00" is that exact instant; "2026-03-01T09:00:00" (no zone) is read in the default timezone (OUTLOOK_DEFAULT_TIMEZONE).',
|
|
420
441
|
},
|
|
421
442
|
startDateTime: {
|
|
422
443
|
type: 'string',
|
|
423
|
-
description:
|
|
444
|
+
description:
|
|
445
|
+
'Start date/time for follow-up (action=flag), same format as dueDateTime. Defaults to 09:00 on the due date in the default timezone (capped at the due time) when only dueDateTime is given.',
|
|
446
|
+
},
|
|
447
|
+
sharedMailbox: {
|
|
448
|
+
type: 'string',
|
|
449
|
+
description:
|
|
450
|
+
'Email address of a shared/delegated mailbox whose message(s) to update instead of the signed-in account. Requires delegate access + Mail.ReadWrite.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).',
|
|
451
|
+
},
|
|
452
|
+
email: {
|
|
453
|
+
type: 'string',
|
|
454
|
+
description: 'Alias for `sharedMailbox`.',
|
|
424
455
|
},
|
|
425
456
|
},
|
|
426
457
|
additionalProperties: false,
|
|
427
458
|
required: ['action'],
|
|
428
459
|
},
|
|
429
460
|
handler: async (args) => {
|
|
461
|
+
const sharedMailbox = args.sharedMailbox || args.email || null;
|
|
430
462
|
switch (args.action) {
|
|
431
463
|
case 'mark-read':
|
|
432
|
-
return handleMarkAsRead({ id: args.id, isRead: true });
|
|
464
|
+
return handleMarkAsRead({ id: args.id, isRead: true, sharedMailbox });
|
|
433
465
|
case 'mark-unread':
|
|
434
|
-
return handleMarkAsRead({
|
|
466
|
+
return handleMarkAsRead({
|
|
467
|
+
id: args.id,
|
|
468
|
+
isRead: false,
|
|
469
|
+
sharedMailbox,
|
|
470
|
+
});
|
|
435
471
|
case 'flag':
|
|
436
472
|
return handleSetMessageFlag({
|
|
437
473
|
messageId: args.id,
|
|
438
474
|
messageIds: args.ids,
|
|
439
475
|
dueDateTime: args.dueDateTime,
|
|
440
476
|
startDateTime: args.startDateTime,
|
|
477
|
+
sharedMailbox,
|
|
441
478
|
});
|
|
442
479
|
case 'unflag':
|
|
443
480
|
return handleClearMessageFlag({
|
|
444
481
|
messageId: args.id,
|
|
445
482
|
messageIds: args.ids,
|
|
446
483
|
markComplete: false,
|
|
484
|
+
sharedMailbox,
|
|
447
485
|
});
|
|
448
486
|
case 'complete':
|
|
449
487
|
return handleClearMessageFlag({
|
|
450
488
|
messageId: args.id,
|
|
451
489
|
messageIds: args.ids,
|
|
452
490
|
markComplete: true,
|
|
491
|
+
sharedMailbox,
|
|
453
492
|
});
|
|
454
493
|
default:
|
|
455
494
|
return {
|
|
@@ -466,7 +505,7 @@ const emailTools = [
|
|
|
466
505
|
{
|
|
467
506
|
name: 'attachments',
|
|
468
507
|
description:
|
|
469
|
-
'Inspect or retrieve email attachments. action=`list` (default) returns metadata for all attachments on `messageId` (id, name, contentType, size, isInline) — read-only. action=`view` returns inline content for text/JSON/XML attachments via `attachmentId`; binary types require download. action=`download` saves the attachment to disk at `outputDir` (default system tmpdir, auto-created) and returns the saved file path. `messageId` is required for all actions; `attachmentId` is required for view/download. Use `outputVerbosity` to control list field count.',
|
|
508
|
+
'Inspect or retrieve email attachments. action=`list` (default) returns metadata for all attachments on `messageId` (id, name, contentType, size, isInline) — read-only. action=`view` returns inline content for text/JSON/XML attachments via `attachmentId`; binary types require download. action=`download` saves the attachment to disk at `outputDir` (default system tmpdir, auto-created) and returns the saved file path. `messageId` is required for all actions; `attachmentId` is required for view/download. If `messageId` came from a shared/delegated mailbox, pass the same `sharedMailbox` (or alias `email`) — attachment IDs are scoped to the message and fail under /me otherwise. Use `outputVerbosity` to control list field count.',
|
|
470
509
|
annotations: {
|
|
471
510
|
title: 'Attachments',
|
|
472
511
|
readOnlyHint: false,
|
|
@@ -491,6 +530,15 @@ const emailTools = [
|
|
|
491
530
|
type: 'string',
|
|
492
531
|
description: 'Attachment ID (action=view/download, required)',
|
|
493
532
|
},
|
|
533
|
+
sharedMailbox: {
|
|
534
|
+
type: 'string',
|
|
535
|
+
description:
|
|
536
|
+
'Email address of the shared/delegated mailbox the messageId belongs to. Required when the message came from a shared mailbox. Requires delegate access + Mail.Read.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).',
|
|
537
|
+
},
|
|
538
|
+
email: {
|
|
539
|
+
type: 'string',
|
|
540
|
+
description: 'Alias for `sharedMailbox`.',
|
|
541
|
+
},
|
|
494
542
|
outputDir: {
|
|
495
543
|
type: 'string',
|
|
496
544
|
description:
|
|
@@ -529,7 +577,7 @@ const emailTools = [
|
|
|
529
577
|
{
|
|
530
578
|
name: 'export',
|
|
531
579
|
description:
|
|
532
|
-
'Export emails to file formats for archival, forensics, or programmatic processing. target=`message` (default) exports a single email by `id` to `savePath` — accepts `mime`/`eml`/`markdown`/`json`/`csv`. target=`messages` batch-exports either an explicit `emailIds` array or messages matching `searchQuery` (or `query` shortcut) into `outputDir` — accepts `markdown`/`json`/`csv`. target=`conversation` exports a full thread by `conversationId` into `outputDir` (chronological by default; pass `order: "reverse"` for newest-first) — accepts `eml`/`mbox`/`markdown`/`json`/`html`/`csv`. target=`mime` returns raw RFC-822 MIME bytes for `id` (use `headersOnly` for just headers, `base64` for encoded transport, `maxSize` to cap at default 1MB). `includeAttachments` defaults to true for single-message exports and false for batch. Format support varies by target — see the format param enum.',
|
|
580
|
+
'Export emails to file formats for archival, forensics, or programmatic processing. target=`message` (default) exports a single email by `id` to `savePath` — accepts `mime`/`eml`/`markdown`/`json`/`csv`. target=`messages` batch-exports either an explicit `emailIds` array or messages matching `searchQuery` (or `query` shortcut) into `outputDir` — accepts `markdown`/`json`/`csv`. target=`conversation` exports a full thread (up to 1000 messages) by `conversationId` into `outputDir` (chronological by default; pass `order: "reverse"` for newest-first) — accepts `eml`/`mbox`/`markdown`/`json`/`html`/`csv`. target=`mime` returns raw RFC-822 MIME bytes for `id` (use `headersOnly` for just headers, `base64` for encoded transport, `maxSize` to cap at default 1MB). All targets accept `sharedMailbox` (alias `email`) to export from a shared/delegated mailbox instead of the signed-in account — pass it whenever the id(s)/conversationId/searchQuery come from a shared mailbox, or exports fail with 404 ErrorInvalidMailboxItemId. `includeAttachments` defaults to true for single-message exports and false for batch. Format support varies by target — see the format param enum.',
|
|
533
581
|
annotations: {
|
|
534
582
|
title: 'Export Emails',
|
|
535
583
|
readOnlyHint: false,
|
|
@@ -606,6 +654,15 @@ const emailTools = [
|
|
|
606
654
|
description:
|
|
607
655
|
'Message order (target=conversation, default: chronological)',
|
|
608
656
|
},
|
|
657
|
+
sharedMailbox: {
|
|
658
|
+
type: 'string',
|
|
659
|
+
description:
|
|
660
|
+
'Email address of a shared/delegated mailbox to export from instead of the signed-in account. Applies to all targets (message/messages/conversation/mime) — pass it whenever the id(s)/conversationId/searchQuery belong to a shared mailbox. Requires delegate access + Mail.Read.Shared. Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).',
|
|
661
|
+
},
|
|
662
|
+
email: {
|
|
663
|
+
type: 'string',
|
|
664
|
+
description: 'Alias for `sharedMailbox`.',
|
|
665
|
+
},
|
|
609
666
|
// MIME params
|
|
610
667
|
headersOnly: {
|
|
611
668
|
type: 'boolean',
|
package/email/list.js
CHANGED
|
@@ -51,13 +51,20 @@ async function handleListEmails(args) {
|
|
|
51
51
|
const requestedCount =
|
|
52
52
|
args.count ?? args.maxResults ?? DEFAULT_LIMITS.listEmails;
|
|
53
53
|
const verbosity = args.outputVerbosity || VERBOSITY.STANDARD;
|
|
54
|
+
// `search-emails` falls through to list mode when no filters are supplied,
|
|
55
|
+
// so the shared-mailbox scope has to be honoured here too.
|
|
56
|
+
const sharedMailbox = args.sharedMailbox || args.email || null;
|
|
54
57
|
|
|
55
58
|
try {
|
|
56
59
|
// Get access token
|
|
57
60
|
const accessToken = await ensureAuthenticated();
|
|
58
61
|
|
|
59
62
|
// Resolve the folder path
|
|
60
|
-
const endpoint = await resolveFolderPath(
|
|
63
|
+
const endpoint = await resolveFolderPath(
|
|
64
|
+
accessToken,
|
|
65
|
+
folder,
|
|
66
|
+
sharedMailbox
|
|
67
|
+
);
|
|
61
68
|
|
|
62
69
|
// Select fields based on verbosity level
|
|
63
70
|
const fieldPreset = getFieldPresetForVerbosity(verbosity);
|
package/email/mark-as-read.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const _config = require('../config'); // Reserved for future use
|
|
5
5
|
const { callGraphAPI } = require('../utils/graph-api');
|
|
6
6
|
const { ensureAuthenticated } = require('../auth');
|
|
7
|
+
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Mark email as read handler
|
|
@@ -13,6 +14,7 @@ const { ensureAuthenticated } = require('../auth');
|
|
|
13
14
|
async function handleMarkAsRead(args) {
|
|
14
15
|
const emailId = args.id;
|
|
15
16
|
const isRead = args.isRead !== undefined ? args.isRead : true; // Default to true
|
|
17
|
+
const prefix = buildMailboxPrefix(args.sharedMailbox || args.email || null);
|
|
16
18
|
|
|
17
19
|
if (!emailId) {
|
|
18
20
|
return {
|
|
@@ -30,7 +32,7 @@ async function handleMarkAsRead(args) {
|
|
|
30
32
|
const accessToken = await ensureAuthenticated();
|
|
31
33
|
|
|
32
34
|
// Make API call to update email read status
|
|
33
|
-
const endpoint =
|
|
35
|
+
const endpoint = `${prefix}/messages/${emailId}`;
|
|
34
36
|
const updateData = {
|
|
35
37
|
isRead: isRead,
|
|
36
38
|
};
|
package/email/mime.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
const { callGraphAPIRaw } = require('../utils/graph-api');
|
|
8
8
|
const { ensureAuthenticated } = require('../auth');
|
|
9
|
+
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Parse MIME headers from raw content
|
|
@@ -93,6 +94,8 @@ async function handleGetMimeContent(args) {
|
|
|
93
94
|
const headersOnly = args.headersOnly || false;
|
|
94
95
|
const returnBase64 = args.base64 || false;
|
|
95
96
|
const maxSize = args.maxSize !== undefined ? args.maxSize : 1024 * 1024; // 1MB default
|
|
97
|
+
// Mailbox-scoped IDs: route to /users/{mailbox} for a shared/delegated mailbox.
|
|
98
|
+
const prefix = buildMailboxPrefix(args.sharedMailbox || args.email || null);
|
|
96
99
|
|
|
97
100
|
if (!emailId) {
|
|
98
101
|
return {
|
|
@@ -111,7 +114,7 @@ async function handleGetMimeContent(args) {
|
|
|
111
114
|
|
|
112
115
|
try {
|
|
113
116
|
// Fetch raw MIME content
|
|
114
|
-
const mimeContent = await callGraphAPIRaw(accessToken, emailId);
|
|
117
|
+
const mimeContent = await callGraphAPIRaw(accessToken, emailId, prefix);
|
|
115
118
|
|
|
116
119
|
if (!mimeContent) {
|
|
117
120
|
return {
|
package/email/read.js
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
VERBOSITY,
|
|
12
12
|
} = require('../utils/response-formatter');
|
|
13
13
|
const { getEmailFields } = require('../utils/field-presets');
|
|
14
|
+
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Get field preset based on verbosity and options
|
|
@@ -45,6 +46,9 @@ async function handleReadEmail(args) {
|
|
|
45
46
|
const emailId = args.id;
|
|
46
47
|
const verbosity = args.outputVerbosity || VERBOSITY.STANDARD;
|
|
47
48
|
const includeHeaders = args.includeHeaders || false;
|
|
49
|
+
// Message IDs are mailbox-scoped: an ID issued by a shared/delegated mailbox
|
|
50
|
+
// is not resolvable under /me. Route to /users/{mailbox} when supplied.
|
|
51
|
+
const prefix = buildMailboxPrefix(args.sharedMailbox || args.email || null);
|
|
48
52
|
|
|
49
53
|
if (!emailId) {
|
|
50
54
|
return {
|
|
@@ -66,7 +70,7 @@ async function handleReadEmail(args) {
|
|
|
66
70
|
const selectFields = getEmailFields(fieldPreset);
|
|
67
71
|
|
|
68
72
|
// Make API call to get email details
|
|
69
|
-
const endpoint =
|
|
73
|
+
const endpoint = `${prefix}/messages/${emailId}`;
|
|
70
74
|
const queryParams = {
|
|
71
75
|
$select: selectFields,
|
|
72
76
|
};
|
package/email/search.js
CHANGED
|
@@ -7,13 +7,18 @@ const _config = require('../config'); // Reserved for future use
|
|
|
7
7
|
const { callGraphAPI, callGraphAPIPaginated } = require('../utils/graph-api');
|
|
8
8
|
const { ensureAuthenticated } = require('../auth');
|
|
9
9
|
const { resolveFolderPath } = require('./folder-utils');
|
|
10
|
+
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
10
11
|
const {
|
|
11
12
|
formatEmailList,
|
|
12
13
|
VERBOSITY,
|
|
13
14
|
DEFAULT_LIMITS,
|
|
14
15
|
} = require('../utils/response-formatter');
|
|
15
16
|
const { getEmailFields } = require('../utils/field-presets');
|
|
16
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
escapeODataString,
|
|
19
|
+
escapeSearchPhrase,
|
|
20
|
+
quoteSearchPhrase,
|
|
21
|
+
} = require('../utils/odata-helpers');
|
|
17
22
|
|
|
18
23
|
// Upper bound on how many recent messages the client-side fallback scans
|
|
19
24
|
// before giving up. Deliberately DECOUPLED from the requested result count so
|
|
@@ -71,6 +76,9 @@ async function handleSearchEmails(args) {
|
|
|
71
76
|
// Trim so a whitespace-only value doesn't send `$search: '""'`. (#169)
|
|
72
77
|
const kqlQuery =
|
|
73
78
|
(args.searchExpression || '').trim() || (args.kqlQuery || '').trim();
|
|
79
|
+
// Optional: scope the search to a shared/delegated mailbox rather than the
|
|
80
|
+
// signed-in account. Accepts a custom/localized folder name or nested path.
|
|
81
|
+
const sharedMailbox = args.sharedMailbox || args.email || null;
|
|
74
82
|
|
|
75
83
|
// Select fields based on verbosity — but never at the cost of correctness.
|
|
76
84
|
// When more than one search term is supplied, the ladder may satisfy one
|
|
@@ -98,13 +106,16 @@ async function handleSearchEmails(args) {
|
|
|
98
106
|
// Get access token
|
|
99
107
|
const accessToken = await ensureAuthenticated();
|
|
100
108
|
|
|
101
|
-
// Determine endpoint - search all folders or specific folder
|
|
109
|
+
// Determine endpoint - search all folders or specific folder, scoped to
|
|
110
|
+
// the signed-in account or a shared mailbox.
|
|
102
111
|
let endpoint;
|
|
103
112
|
if (searchAllFolders) {
|
|
104
|
-
endpoint =
|
|
105
|
-
console.error(
|
|
113
|
+
endpoint = `${buildMailboxPrefix(sharedMailbox)}/messages`;
|
|
114
|
+
console.error(
|
|
115
|
+
`Searching across all mail folders${sharedMailbox ? ` in ${sharedMailbox}` : ''}`
|
|
116
|
+
);
|
|
106
117
|
} else {
|
|
107
|
-
endpoint = await resolveFolderPath(accessToken, folder);
|
|
118
|
+
endpoint = await resolveFolderPath(accessToken, folder, sharedMailbox);
|
|
108
119
|
console.error(`Using endpoint: ${endpoint} for folder: ${folder}`);
|
|
109
120
|
}
|
|
110
121
|
|
|
@@ -321,12 +332,13 @@ async function progressiveSearch(
|
|
|
321
332
|
trimmedKql.includes(':') || /\s/.test(trimmedKql);
|
|
322
333
|
// Already-quoted phrases and KQL-looking expressions (field syntax
|
|
323
334
|
// or multi-word) are passed through as-is; only bare single tokens
|
|
324
|
-
// are wrapped so Graph treats them as phrase searches.
|
|
335
|
+
// are wrapped so Graph treats them as phrase searches. The wrapped
|
|
336
|
+
// token's own `"` and `\` are escaped so they cannot end the phrase. (#251)
|
|
325
337
|
let kqlForSearch;
|
|
326
338
|
if (alreadyQuoted || looksLikeExpression) {
|
|
327
339
|
kqlForSearch = trimmedKql;
|
|
328
340
|
} else {
|
|
329
|
-
kqlForSearch = `"${trimmedKql}"`;
|
|
341
|
+
kqlForSearch = `"${escapeSearchPhrase(trimmedKql)}"`;
|
|
330
342
|
}
|
|
331
343
|
|
|
332
344
|
// Graph rejects field-scoped expressions outright on personal accounts
|
|
@@ -1205,7 +1217,7 @@ function buildSearchParams(searchTerms, filterTerms, count, selectFields) {
|
|
|
1205
1217
|
|
|
1206
1218
|
// Handle search terms - use $search only for free-text query
|
|
1207
1219
|
if (searchTerms.query) {
|
|
1208
|
-
params.$search =
|
|
1220
|
+
params.$search = quoteSearchPhrase(searchTerms.query);
|
|
1209
1221
|
}
|
|
1210
1222
|
|
|
1211
1223
|
// Build filter conditions array - use $filter for structured fields (more reliable)
|
|
@@ -1569,6 +1581,8 @@ function formatSearchResults(response, folder, verbosity, searchAllFolders) {
|
|
|
1569
1581
|
async function handleSearchByMessageId(args) {
|
|
1570
1582
|
const messageId = args.messageId;
|
|
1571
1583
|
const verbosity = args.outputVerbosity || VERBOSITY.STANDARD;
|
|
1584
|
+
// Optional: scope the lookup to a shared/delegated mailbox.
|
|
1585
|
+
const prefix = buildMailboxPrefix(args.sharedMailbox || args.email || null);
|
|
1572
1586
|
|
|
1573
1587
|
if (!messageId) {
|
|
1574
1588
|
return {
|
|
@@ -1604,7 +1618,7 @@ async function handleSearchByMessageId(args) {
|
|
|
1604
1618
|
const response = await callGraphAPI(
|
|
1605
1619
|
accessToken,
|
|
1606
1620
|
'GET',
|
|
1607
|
-
|
|
1621
|
+
`${prefix}/messages`,
|
|
1608
1622
|
null,
|
|
1609
1623
|
params
|
|
1610
1624
|
);
|
package/folder/create.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
5
|
const { ensureAuthenticated } = require('../auth');
|
|
6
6
|
const { resolveFolder, listChildFolders } = require('./resolve');
|
|
7
|
+
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Create folder handler
|
|
@@ -14,6 +15,7 @@ async function handleCreateFolder(args) {
|
|
|
14
15
|
const folderName = (args.name || '').trim();
|
|
15
16
|
const parentFolder = args.parentFolder || '';
|
|
16
17
|
const parentFolderId = args.parentFolderId || '';
|
|
18
|
+
const sharedMailbox = args.sharedMailbox || args.email || null;
|
|
17
19
|
|
|
18
20
|
if (!folderName) {
|
|
19
21
|
return {
|
|
@@ -34,6 +36,7 @@ async function handleCreateFolder(args) {
|
|
|
34
36
|
const result = await createMailFolder(accessToken, folderName, {
|
|
35
37
|
name: parentFolder,
|
|
36
38
|
id: parentFolderId,
|
|
39
|
+
mailbox: sharedMailbox,
|
|
37
40
|
});
|
|
38
41
|
|
|
39
42
|
return {
|
|
@@ -74,10 +77,12 @@ async function handleCreateFolder(args) {
|
|
|
74
77
|
* Create a new mail folder
|
|
75
78
|
* @param {string} accessToken - Access token
|
|
76
79
|
* @param {string} folderName - Name of the folder to create
|
|
77
|
-
* @param {{name?: string, id?: string}} parentSpec - Parent folder name/path or ID
|
|
80
|
+
* @param {{name?: string, id?: string, mailbox?: string|null}} parentSpec - Parent folder name/path or ID, plus optional shared mailbox
|
|
78
81
|
* @returns {Promise<object>} - Result object with status and message
|
|
79
82
|
*/
|
|
80
83
|
async function createMailFolder(accessToken, folderName, parentSpec) {
|
|
84
|
+
const mailbox = parentSpec.mailbox || null;
|
|
85
|
+
const prefix = buildMailboxPrefix(mailbox);
|
|
81
86
|
try {
|
|
82
87
|
// Resolve the parent folder if one was specified (supports "Parent/Child"
|
|
83
88
|
// paths and explicit IDs). Leaf name (folderName) is created, not
|
|
@@ -98,7 +103,9 @@ async function createMailFolder(accessToken, folderName, parentSpec) {
|
|
|
98
103
|
// mailbox — a name may legitimately exist under a different parent. (#216)
|
|
99
104
|
const siblings = await listChildFolders(
|
|
100
105
|
accessToken,
|
|
101
|
-
parent ? parent.id : null
|
|
106
|
+
parent ? parent.id : null,
|
|
107
|
+
undefined,
|
|
108
|
+
mailbox
|
|
102
109
|
);
|
|
103
110
|
const lower = folderName.toLowerCase();
|
|
104
111
|
if (siblings.some((f) => f.displayName.toLowerCase() === lower)) {
|
|
@@ -111,8 +118,8 @@ async function createMailFolder(accessToken, folderName, parentSpec) {
|
|
|
111
118
|
}
|
|
112
119
|
|
|
113
120
|
const endpoint = parent
|
|
114
|
-
?
|
|
115
|
-
:
|
|
121
|
+
? `${prefix}/mailFolders/${parent.id}/childFolders`
|
|
122
|
+
: `${prefix}/mailFolders`;
|
|
116
123
|
|
|
117
124
|
// Create the folder
|
|
118
125
|
const folderData = {
|
package/folder/delete.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
5
|
const { ensureAuthenticated } = require('../auth');
|
|
6
6
|
const { resolveFolder, WELL_KNOWN } = require('./resolve');
|
|
7
|
+
const { buildMailboxPrefix } = require('../utils/mailbox');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Delete folder handler
|
|
@@ -22,6 +23,8 @@ const { resolveFolder, WELL_KNOWN } = require('./resolve');
|
|
|
22
23
|
*/
|
|
23
24
|
async function handleDeleteFolder(args) {
|
|
24
25
|
const { folderId, folderName } = args;
|
|
26
|
+
const sharedMailbox = args.sharedMailbox || args.email || null;
|
|
27
|
+
const prefix = buildMailboxPrefix(sharedMailbox);
|
|
25
28
|
|
|
26
29
|
if (!folderId && !folderName) {
|
|
27
30
|
return {
|
|
@@ -56,6 +59,7 @@ async function handleDeleteFolder(args) {
|
|
|
56
59
|
resolved = await resolveFolder(accessToken, {
|
|
57
60
|
id: folderId,
|
|
58
61
|
name: folderName,
|
|
62
|
+
mailbox: sharedMailbox,
|
|
59
63
|
});
|
|
60
64
|
} catch (resolveError) {
|
|
61
65
|
return {
|
|
@@ -64,7 +68,11 @@ async function handleDeleteFolder(args) {
|
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
// Delete the folder
|
|
67
|
-
await callGraphAPI(
|
|
71
|
+
await callGraphAPI(
|
|
72
|
+
accessToken,
|
|
73
|
+
'DELETE',
|
|
74
|
+
`${prefix}/mailFolders/${resolved.id}`
|
|
75
|
+
);
|
|
68
76
|
return {
|
|
69
77
|
content: [
|
|
70
78
|
{
|
package/folder/index.js
CHANGED
|
@@ -12,7 +12,7 @@ const folderTools = [
|
|
|
12
12
|
{
|
|
13
13
|
name: 'folders',
|
|
14
14
|
description:
|
|
15
|
-
"Manage mail folders (tool-level destructiveHint=true because `delete`
|
|
15
|
+
"Manage mail folders (tool-level destructiveHint=true because `delete` removes a folder and its contents; `list` and `stats` are read-only sub-actions despite the annotation). Folders can be addressed by name, by a slash-separated PATH for nested folders (e.g. `Triage/Delete`, `Inbox/Clients/Acme`, case-insensitive), or by explicit ID; `list` output includes each folder's full path and `[id: …]`. A bare name resolves a unique top-level folder first, then searches nested folders (ambiguous names return the candidates — disambiguate with a path or ID). action=`list` (default) returns the folder tree (toggle `includeItemCounts` for unread/total, `includeChildren` for hierarchy). action=`create` makes a new folder under the root, or under `parentFolder` (name/path) / `parentFolderId`, and returns its id. action=`move` relocates emails (`emailIds`) into `targetFolder` (name/path) or `targetFolderId`. action=`stats` returns counts (totalItemCount/unreadItemCount) for `folder` (name/path) or `folderId`, suitable for pagination planning. action=`delete` removes a folder (by `folderName`/path or `folderId`) and its contents — it does not go to Deleted Items, and Graph doesn't document whether it can be restored (some accounts may offer Outlook's \"Recover deleted items\" for a limited time, but don't rely on it), so move out anything you might need first. Every action accepts `sharedMailbox` (alias `email`) to target a shared/delegated mailbox instead of the signed-in account — folder names, paths, and IDs are then resolved inside that mailbox. Protected folders cannot be deleted in any mailbox.",
|
|
16
16
|
annotations: {
|
|
17
17
|
title: 'Mail Folders',
|
|
18
18
|
readOnlyHint: false,
|
|
@@ -36,6 +36,16 @@ const folderTools = [
|
|
|
36
36
|
type: 'boolean',
|
|
37
37
|
description: 'Include child folders in hierarchy (action=list)',
|
|
38
38
|
},
|
|
39
|
+
// shared-mailbox scoping (all actions)
|
|
40
|
+
sharedMailbox: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
description:
|
|
43
|
+
'Email address of a shared/delegated mailbox to target instead of the signed-in account (all actions). Requires delegate access + Mail.Read.Shared (list/stats) or Mail.ReadWrite.Shared (create/move/delete). Work/school only; needs the server opt-in setting OUTLOOK_SHARED_MAILBOX (otherwise the call is refused with setup guidance).',
|
|
44
|
+
},
|
|
45
|
+
email: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
description: 'Alias for `sharedMailbox`.',
|
|
48
|
+
},
|
|
39
49
|
// create params
|
|
40
50
|
name: {
|
|
41
51
|
type: 'string',
|