@nocobase/app-plugin-mail 1.0.0-beta.11 → 1.0.0-beta.13
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/CHANGELOG.md +33 -0
- package/README.md +35 -2
- package/dist/client/components/mail-account-connector.d.ts +1 -0
- package/dist/client/components/mail-account-connector.d.ts.map +1 -1
- package/dist/client/components/mail-account-connector.js +4 -3
- package/dist/client/components/mail-account-connector.js.map +1 -1
- package/dist/client/components/mail-navigation-icon.d.ts.map +1 -1
- package/dist/client/components/mail-navigation-icon.js +3 -50
- package/dist/client/components/mail-navigation-icon.js.map +1 -1
- package/dist/client/components/public.d.ts +1 -1
- package/dist/client/components/public.d.ts.map +1 -1
- package/dist/client/components/public.js.map +1 -1
- package/dist/client/components/ui/field.d.ts +7 -0
- package/dist/client/components/ui/field.d.ts.map +1 -0
- package/dist/client/components/ui/field.js +20 -0
- package/dist/client/components/ui/field.js.map +1 -0
- package/dist/client/contracts/composer.d.ts +29 -2
- package/dist/client/contracts/composer.d.ts.map +1 -1
- package/dist/client/contracts/composer.js.map +1 -1
- package/dist/client/hooks/use-mail-composer.d.ts +1 -1
- package/dist/client/hooks/use-mail-composer.d.ts.map +1 -1
- package/dist/client/hooks/use-mail-composer.js +54 -10
- package/dist/client/hooks/use-mail-composer.js.map +1 -1
- package/dist/client/hooks/use-mail-unread-count.d.ts +13 -0
- package/dist/client/hooks/use-mail-unread-count.d.ts.map +1 -0
- package/dist/client/hooks/use-mail-unread-count.js +106 -0
- package/dist/client/hooks/use-mail-unread-count.js.map +1 -0
- package/dist/client/hooks/use-mail-workspace-data.d.ts +3 -1
- package/dist/client/hooks/use-mail-workspace-data.d.ts.map +1 -1
- package/dist/client/hooks/use-mail-workspace-data.js +14 -2
- package/dist/client/hooks/use-mail-workspace-data.js.map +1 -1
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/lib/mail-composer-completion.d.ts +5 -0
- package/dist/client/lib/mail-composer-completion.d.ts.map +1 -0
- package/dist/client/lib/mail-composer-completion.js +28 -0
- package/dist/client/lib/mail-composer-completion.js.map +1 -0
- package/dist/client/locales/en-US.d.ts +4 -0
- package/dist/client/locales/en-US.d.ts.map +1 -1
- package/dist/client/locales/en-US.js +7 -3
- package/dist/client/locales/en-US.js.map +1 -1
- package/dist/client/locales/zh-CN.d.ts.map +1 -1
- package/dist/client/locales/zh-CN.js +6 -2
- package/dist/client/locales/zh-CN.js.map +1 -1
- package/dist/client/mail-client.d.ts +5 -1
- package/dist/client/mail-client.d.ts.map +1 -1
- package/dist/client/mail-client.js.map +1 -1
- package/dist/client/pages/mail-accounts-page.d.ts.map +1 -1
- package/dist/client/pages/mail-accounts-page.js +4 -1
- package/dist/client/pages/mail-accounts-page.js.map +1 -1
- package/dist/client/pages/mail-workspace-page.d.ts +4 -2
- package/dist/client/pages/mail-workspace-page.d.ts.map +1 -1
- package/dist/client/pages/mail-workspace-page.js +253 -173
- package/dist/client/pages/mail-workspace-page.js.map +1 -1
- package/dist/client/prepare-mail-reply.d.ts +9 -0
- package/dist/client/prepare-mail-reply.d.ts.map +1 -0
- package/dist/client/prepare-mail-reply.js +28 -0
- package/dist/client/prepare-mail-reply.js.map +1 -0
- package/dist/database/migrations/.manifest.json +6 -2
- package/dist/database/migrations/202609030001_create_mail_tables.d.ts.map +1 -1
- package/dist/database/migrations/202609030001_create_mail_tables.js +8 -4
- package/dist/database/migrations/202609030001_create_mail_tables.js.map +1 -1
- package/dist/database/migrations/202610090001_mail_create_message_participants.d.ts +4 -0
- package/dist/database/migrations/202610090001_mail_create_message_participants.d.ts.map +1 -0
- package/dist/database/migrations/202610090001_mail_create_message_participants.js +167 -0
- package/dist/database/migrations/202610090001_mail_create_message_participants.js.map +1 -0
- package/dist/server/config.d.ts +1 -1
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +17 -9
- package/dist/server/config.js.map +1 -1
- package/dist/server/locales/en-US.d.ts +1 -0
- package/dist/server/locales/en-US.d.ts.map +1 -1
- package/dist/server/locales/en-US.js +1 -0
- package/dist/server/locales/en-US.js.map +1 -1
- package/dist/server/locales/zh-CN.d.ts.map +1 -1
- package/dist/server/locales/zh-CN.js +1 -0
- package/dist/server/locales/zh-CN.js.map +1 -1
- package/dist/server/providers/mail-core.d.ts +2 -0
- package/dist/server/providers/mail-core.d.ts.map +1 -1
- package/dist/server/providers/mail-core.js +39 -1
- package/dist/server/providers/mail-core.js.map +1 -1
- package/dist/server/routes/api.d.ts.map +1 -1
- package/dist/server/routes/api.js +6 -2
- package/dist/server/routes/api.js.map +1 -1
- package/dist/server/routes/schemas.d.ts +2 -0
- package/dist/server/routes/schemas.d.ts.map +1 -1
- package/dist/server/routes/schemas.js +13 -0
- package/dist/server/routes/schemas.js.map +1 -1
- package/dist/server/services/authorization.js +1 -1
- package/dist/server/services/authorization.js.map +1 -1
- package/dist/server/store/account-removals.d.ts.map +1 -1
- package/dist/server/store/account-removals.js +6 -0
- package/dist/server/store/account-removals.js.map +1 -1
- package/dist/server/store/draft-states.d.ts.map +1 -1
- package/dist/server/store/draft-states.js +2 -0
- package/dist/server/store/draft-states.js.map +1 -1
- package/dist/server/store/message-participants.d.ts +25 -0
- package/dist/server/store/message-participants.d.ts.map +1 -0
- package/dist/server/store/message-participants.js +109 -0
- package/dist/server/store/message-participants.js.map +1 -0
- package/dist/server/store/message-writes.d.ts.map +1 -1
- package/dist/server/store/message-writes.js +3 -0
- package/dist/server/store/message-writes.js.map +1 -1
- package/dist/server/store/messages.d.ts.map +1 -1
- package/dist/server/store/messages.js +15 -0
- package/dist/server/store/messages.js.map +1 -1
- package/dist/shared/mail.d.ts +3 -1
- package/dist/shared/mail.d.ts.map +1 -1
- package/dist/shared/mail.js.map +1 -1
- package/dist/shared/participant.d.ts +14 -0
- package/dist/shared/participant.d.ts.map +1 -0
- package/dist/shared/participant.js +45 -0
- package/dist/shared/participant.js.map +1 -0
- package/package.json +10 -7
- package/skills/nocobase-app-plugin-mail/SKILL.md +9 -7
- package/skills/nocobase-app-plugin-mail/references/client-integration.md +125 -1
- package/skills/nocobase-app-plugin-mail/references/configuration-and-accounts.md +10 -4
- package/skills/nocobase-app-plugin-mail/references/http-api.md +32 -24
- package/skills/nocobase-app-plugin-mail/references/public-api.md +27 -20
- package/skills/nocobase-app-plugin-mail/references/synchronization-and-diagnostics.md +14 -0
|
@@ -12,6 +12,23 @@ The plugin contributes no routes. An application places `MailWorkspacePage`, `Ma
|
|
|
12
12
|
|
|
13
13
|
If account connection is included, configure `mail.oauthReturnUrl` or `MAIL_OAUTH_RETURN_URL` to the application page that renders the accounts and read [OAuth callback and return page](configuration-and-accounts.md#oauth-callback-and-return-page); the default returns to the application root. The return page only consumes `mailAuthorization` and refreshes account state; it does not need `MailAccountConnector` unless it also starts a separate new-account flow.
|
|
14
14
|
|
|
15
|
+
### Personal account entry and workspace actions
|
|
16
|
+
|
|
17
|
+
Register an application-owned `/mail/accounts` page whose lazy loader imports the public `MailAccountsPage` from `/client`. Require authentication (`auth: 'required'`) and declare `authz: { resource: { type: 'page', id: 'mail.workspace' }, action: 'access' }` on that page and the workspace. The account APIs independently enforce permissions; a link grants no access. This is a personal connection/management page, including resuming suspended accounts, not a read-only administrator account overview.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
<MailWorkspacePage
|
|
21
|
+
accountsHref='/mail/accounts'
|
|
22
|
+
headerActions={<a href={resolveAppUrl('/mail/logs')}>Mail logs</a>}
|
|
23
|
+
/>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Import `resolveAppUrl` from `@nocobase/app-client` for application-owned links. `accountsHref` excludes the deployment prefix: `/mail/accounts` becomes `/main/mail/accounts` under `/main`. With no usable accounts the workspace displays Connect mail account; with accounts it retains Mail accounts in the header. If the property is omitted, neither link is invented. `headerActions` appends arbitrary React content after the built-in controls without replacing their disabled/loading behavior, and never implicitly supplies an empty-state account link. The reusable workspace does not depend on development mode, and the plugin contributes no routes.
|
|
27
|
+
|
|
28
|
+
Separately configure `mail.oauthReturnUrl: /mail/accounts` so OAuth success and failure return to the registered production page. This server configuration is independent of the component property; the return page consumes the authorization result and reloads accounts. Passing a link does not register a page; the application must declare it in its production routes.
|
|
29
|
+
|
|
30
|
+
A production startup warning flags the known development-only return destination without changing it or creating a route. It is an observability reminder, not proof that a custom route exists. Keep the return path free of the deployment prefix, register the page in production with the authentication/permission contract above, and verify both success and failure flows; the existing `MailAccountsPage` already handles those result parameters.
|
|
31
|
+
|
|
15
32
|
## Business records and templates
|
|
16
33
|
|
|
17
34
|
Pass the allowed record values through `templateVariables` when embedding the workspace:
|
|
@@ -30,6 +47,24 @@ export function CustomerMail() {
|
|
|
30
47
|
|
|
31
48
|
`{{record.customer.name}}` resolves when applying a template. Unknown variables stay visible. Applying a template replaces subject and body. This does not associate messages with a customer or filter correspondence automatically; implement explicitly requested record relationships in the application's domain layer.
|
|
32
49
|
|
|
50
|
+
## CRM correspondence and participant filtering
|
|
51
|
+
|
|
52
|
+
Use the application-owned client to load exact correspondence, independently of template variables:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const mail = app.services.resolve(mailClientToken);
|
|
56
|
+
const customerMail = await mail.listMessages({
|
|
57
|
+
participant: 'alice@example.com',
|
|
58
|
+
});
|
|
59
|
+
const companyMail = await mail.listMessages({ participant: '@example.com' });
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
In React, obtain `mail` with `useMailClient()`. `participant` is optional in `MailMessagesQuery` and `MailManagedMessagesQuery` and is forwarded unchanged to the API. The server trims surrounding whitespace and lowercases the entire mailbox/domain before an exact From/To/Cc match. It accepts ASCII dot-atom mailboxes and dotted DNS domains, not display-name forms, quoted local parts or Unicode addresses. Domain input must start with `@`; `@example.com` excludes subdomains, similar domains and suffix tricks. There is no alias or `+tag` folding. Bcc, Reply-To, display names, subject, preview and body do not participate. `q` keeps its existing fuzzy behavior, including display names and Bcc, and intersects with participant and other filters. Results include only locally synchronized mail within the caller's existing account permissions; do not retry a denied personal request against management APIs.
|
|
63
|
+
|
|
64
|
+
The workspace exposes a separate Participant field with full-address/`@domain` help and accessible validation errors. It uses the existing 300 ms keyword debounce interval, resets page tokens, list and reader selection immediately on every participant edit or clear, and ignores stale responses. Invalid input issues no list request. Clearing omits the parameter instead of sending an empty string. A custom CRM page must likewise clear tokens/selections, guard outstanding responses and block invalid filters; an explicit empty, invalid or oversized API value returns `400 INVALID_INPUT` with a `participant` field violation. `MailWorkspacePageProps` does not provide an externally controlled participant prop; own CRM filtering state in a custom page rather than assuming template variables set the filter.
|
|
65
|
+
|
|
66
|
+
List totals and paging apply the filter before pagination; opening a conversation still loads the complete thread, and the conversation badge keeps its existing full-thread count. Separate IMAP folder copies are not deduplicated by this filter. Verify historical results only after the [participant index upgrade](synchronization-and-diagnostics.md#participant-index-upgrade) has completed.
|
|
67
|
+
|
|
33
68
|
## Build a custom mail page
|
|
34
69
|
|
|
35
70
|
Use `useMailClient()` for typed data access and own page state in the application. `listMessages()` accepts `MAIL_VIRTUAL_FOLDER_IDS` for built-in cross-account folders; `getMessage()` or `listConversationMessages()` loads detail; mutation methods such as `updateMessage()`, `moveMessage()` and `sendMessage()` perform the corresponding server operation. A custom page can combine these calls with `MailMessageList`, `MailConversationView`, `MailboxSidebar` and `MailComposer`, or render its own UI. The application owns filtering, loading/error state, cursor handling, selected-message state and refreshing after the user-scoped realtime invalidation event.
|
|
@@ -75,17 +110,106 @@ Use `GET /api/mail/messages/countUnread` as authoritative. Reuse the plugin's us
|
|
|
75
110
|
|
|
76
111
|
Enable mutations only when the account and provider support them. Consult [IMAP/SMTP boundaries](configuration-and-accounts.md#imapsmtp-boundaries-and-sent-copies) when using the generic adapter. For incomplete content, use [Synchronization and diagnostics](synchronization-and-diagnostics.md#incomplete-content).
|
|
77
112
|
|
|
113
|
+
### Unread counts on custom menus
|
|
114
|
+
|
|
115
|
+
Use `useMailUnreadCount()` from `@nocobase/app-plugin-mail/client` to reuse the same current-user fetching and refresh coordination as `MailNavigationIcon`. Register the Mail Client plugin and render under the host application's service context. `MailUnreadCountState` exposes `unreadCount: number | undefined`, `loading: boolean`, and `error: unknown`. An unknown count is not zero: `undefined` remains until the first successful response, while `0` is a real server result. Background requests keep the last successful count; failures preserve it and expose the original error, and the next successful response clears the error. Use `mailErrorMessage(error, fallback)` when showing an error rather than turning failure into a zero badge.
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import type { ReactElement } from 'react';
|
|
119
|
+
import {
|
|
120
|
+
useMailUnreadCount,
|
|
121
|
+
type MailUnreadCountState,
|
|
122
|
+
} from '@nocobase/app-plugin-mail/client';
|
|
123
|
+
|
|
124
|
+
function CustomMailBadge(): ReactElement {
|
|
125
|
+
const state: MailUnreadCountState = useMailUnreadCount();
|
|
126
|
+
return (
|
|
127
|
+
<span aria-busy={state.loading}>
|
|
128
|
+
{state.unreadCount === undefined ? '—' : state.unreadCount}
|
|
129
|
+
</span>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Mount the consumer inside the application's authenticated, session-owned scope and remount it when the signed-in user changes, for example `<CustomMailBadge key={sessionKey} />` where the application supplies a key that changes on session replacement. The Hook cannot detect a cookie/session change while the same host services remain mounted. Do not retain it across logout or user switching without remounting: a retained component can otherwise display the previous user's count. Cleanup discards late responses from the previous mount; replacing the host client also resets the state. This is not a new global cache or an authentication observer.
|
|
135
|
+
|
|
136
|
+
The Hook fetches on mount and handles local workspace invalidation, server `mail.changed` events, realtime connection open/recovery, window focus throttled to 30 seconds, and a 60-second polling fallback. It retains the existing 100ms debounce and serialized requests: invalidation during a request discards that response and schedules one trailing refresh. `loading` covers an active request and its trailing refresh; an idle debounce can still show the last settled state until the request starts. Invalidations carry no authoritative count. The Hook neither changes read state nor provides account/folder filters, conversation deduplication or independent permission checks; the existing current-user endpoint supplies the count and enforces access.
|
|
137
|
+
|
|
138
|
+
Each mounted Hook instance has its own requests, subscriptions and timers. Two simultaneous consumers may fetch independently; call once in an application-owned parent and distribute its state if several menus need it. Unmount removes listeners and timers but does not abort an already-issued HTTP request; its result is ignored. Low-level `MAIL_UNREAD_COUNT_CHANGED_EVENT` and `subscribeToMailInvalidations` remain private implementation details, not new supported exports or deep-import paths. Keep using `MailNavigationIcon` for the standard translated badge with zero hidden and `99+` display.
|
|
139
|
+
|
|
78
140
|
## Composition and drafts
|
|
79
141
|
|
|
80
142
|
Keep composer state and errors independent of mailbox queries so a refresh does not discard edits. The shared composer retains unfinished content per account while mounted and uses local draft recovery across reloads. Preserve unsaved-change handling, attachment identities, and the separation between forwarded source content and the sender's editable comment.
|
|
81
143
|
|
|
82
144
|
Signatures belong to accounts and are shared across their sending addresses, with one default and selectable alternatives. Templates belong to the current user. For custom sending, scheduled delivery, batch actions or draft persistence, read [Sending and drafts](sending-and-drafts.md) before replacing those interactions.
|
|
83
145
|
|
|
146
|
+
### Prepare replies on custom pages
|
|
147
|
+
|
|
148
|
+
Use `prepareMailReply(mail, message)` from `/client` rather than importing private quote helpers or copying the workspace's reply logic. It returns `Promise<MailComposerRequest>`; that type is also exported from `/client` and remains available from `/client/components`. Pass the returned object unchanged as the `request` of `MailComposer` or `MailWorkspaceComposer`, alongside the required account, provider and callback props.
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import {
|
|
152
|
+
prepareMailReply,
|
|
153
|
+
type MailClient,
|
|
154
|
+
type MailMessage,
|
|
155
|
+
type MailComposerRequest,
|
|
156
|
+
} from '@nocobase/app-plugin-mail/client';
|
|
157
|
+
|
|
158
|
+
async function openPreparedReply(
|
|
159
|
+
mail: MailClient,
|
|
160
|
+
message: MailMessage,
|
|
161
|
+
isCurrent: () => boolean,
|
|
162
|
+
open: (request: MailComposerRequest) => void,
|
|
163
|
+
): Promise<void> {
|
|
164
|
+
const request = await prepareMailReply(mail, message);
|
|
165
|
+
if (isCurrent()) open(request);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Call this from `MailConversationView.actions.reply` with the application-owned client from `useMailClient()`. The application owns pending/error state and prevents duplicate preparation. Capture a session/version before awaiting; `isCurrent()` must reject results after unmount, cancellation, selection changes, or another composer session opens (even if that session has since closed). Do not overwrite an active composer. Catch preparation errors and show `mailErrorMessage(cause, fallback)`; do not open a reply without its quote after failure. The built-in workspace uses this same preparation function and retains its existing composer guards.
|
|
170
|
+
|
|
171
|
+
Preparation uses the message's account and local ID, prefers all Reply-To addresses over From, preserves an existing `Re:` prefix, and leaves CC/BCC and the editable body empty. The quote stays in `value.forwardQuote` with kind `reply`; ordinary source attachments are not added to the top-level `attachments`. Keep `uploads`: only CID images referenced by the quote are copied into owned uploads, and dropping them breaks sending and draft recovery. Composer identity selection, signatures, quote removal/restoration and reply association remain composer responsibilities; this function neither opens UI nor sends or saves mail.
|
|
172
|
+
|
|
173
|
+
This is a browser-only API requiring `DOMParser` and `File`, not a Node/SSR helper. It may retry deferred/failed content and download/upload referenced images, and rejects when those operations fail. A partial failure can leave temporary uploads under the existing attachment lifecycle; preparation is not a transaction or cancellation API. Preview the quote only through the shared script-disabled sandbox, never by injecting its received HTML/CSS into the application's DOM. Preparation does not grant access or bypass the server's ownership and permission checks.
|
|
174
|
+
|
|
175
|
+
### Associate composer completion with business records
|
|
176
|
+
|
|
177
|
+
Both composers keep `onComplete(result, rejectedRecipients?, error?, details?)`. The first three arguments are unchanged; `details` is an optional `MailComposerCompletionDetails` exported from `/client/components`. For `kind: 'normal'`, `input` is the actual single-message request; for `kind: 'bulk'`, it is the actual separate-send request, including deduplicated `recipients` and copied attachment IDs. These detached snapshots preserve account, identity, subject, body (including the editor's signature and reply/forward quote), scheduling, and attachment references before the composer is cleared. They describe client-submitted content, not final MIME, server-prepared content or proof of delivery. Internal delivery snapshots and provider context are not part of this public contract.
|
|
178
|
+
|
|
179
|
+
`details.submissions` contains every record returned by this specific send operation, with its real status and public error unchanged. Never query the latest history row to identify this send: concurrent operations can complete out of order, and a bulk send returns multiple records. The legacy aggregate result is only a UI summary; in particular, bulk records may still be `pending` even when the summary is `accepted`. Treat scheduled/pending, accepted, failed, partial recipient rejection and unknown outcomes separately rather than recording every completion as sent or delivered.
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
import type { MailSubmissionView } from '@nocobase/app-plugin-mail/client';
|
|
183
|
+
import type {
|
|
184
|
+
MailComposerProps,
|
|
185
|
+
MailComposerSubmissionSnapshot,
|
|
186
|
+
} from '@nocobase/app-plugin-mail/client/components';
|
|
187
|
+
|
|
188
|
+
function customerMailCompletion(
|
|
189
|
+
upsertActivity: (
|
|
190
|
+
submission: MailSubmissionView,
|
|
191
|
+
snapshot: MailComposerSubmissionSnapshot,
|
|
192
|
+
) => Promise<void>,
|
|
193
|
+
): MailComposerProps['onComplete'] {
|
|
194
|
+
return async (_result, _rejectedRecipients, _error, details) => {
|
|
195
|
+
if (!details || details.kind === 'draft') return;
|
|
196
|
+
for (const submission of details.submissions) {
|
|
197
|
+
// Use submission.id as the activity's unique key; preserve submission.status.
|
|
198
|
+
await upsertActivity(submission, details);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
A transport failure after submission returns `unknown` with the request snapshot and an empty submissions list when the response was not received. The server may already have created or sent the record: do not fabricate an ID, use the newest history entry, or automatically resend. A saved draft instead returns `kind: 'draft'`, a separate `{ id, accountId }` draft reference and no submissions; it is not a sent-mail activity.
|
|
205
|
+
|
|
206
|
+
Completion callbacks may return `void` or `Promise<void>`. Legacy callbacks that incidentally return a value remain assignable; return values are discarded, but returned promises are observed for failures. The composer closes at its existing time without waiting for activity persistence. Synchronous throws and asynchronous rejections are isolated from the send result and reported through optional `onCompletionError(error)`; they never cause a second completion or send retry. Handle/display integration failures in that handler or inside `onComplete`. A missing or failing error handler emits only a generic diagnostic, never the exception or message content. The application must persist activities idempotently by submission ID and provide server-side reconciliation when reliability matters: closing the browser can prevent a client callback, and this is not a durable server event.
|
|
207
|
+
|
|
84
208
|
## Management and pagination
|
|
85
209
|
|
|
86
210
|
All-user detail and attachment APIs live under `/api/mail/management/accounts/:accountId/messages/:messageId`, with `/attachments/:attachmentId` for downloads. They require management permission. Opening management detail is read-only and does not mark mail read; draft rows use draft status and skip read-state actions. Moving to folders belongs to the personal workspace. Keep personal and all-user client paths explicit instead of retrying permission failures against a broader API.
|
|
87
211
|
|
|
88
|
-
The Mail center pages by cursor, 50 messages per page: `listMessages()` and `listConversationMessages()` take `pageSize` and `pageToken`, and return `{ items, total, nextCursor }`, where `nextCursor` is the opaque token to send back as the next `pageToken` and is absent on the last page. Management and log tables page by number with page-size choices 20, 50 and 100: `listManagedMessages()`, `listSyncRunsPage()` and `listSubmissionsPage()` take `{ page, pageSize }` (page from 1, `pageSize` at most 100) and return `{ items, total }`. Reset pagination after filter or page-size changes.
|
|
212
|
+
The Mail center pages by cursor, 50 messages per page: `listMessages()` and `listConversationMessages()` take `pageSize` and `pageToken`, and return `{ items, total, nextCursor }`, where `nextCursor` is the opaque token to send back as the next `pageToken` and is absent on the last page. Management and log tables page by number with page-size choices 20, 50 and 100: `listManagedMessages()`, `listSyncRunsPage()` and `listSubmissionsPage()` take `{ page, pageSize }` (page from 1, `pageSize` at most 100) and return `{ items, total }`. Reset pagination and selection after filter or page-size changes. Keyword search is `q`; exact From/To/Cc filtering is the independent optional `participant`.
|
|
89
213
|
|
|
90
214
|
Grouped bulk history counts complete batches, not recipients: call `listSubmissionsPage({ bulkOnly: true, groupByBatch: true, page, pageSize })`. Keep expandable recipient results inside their batch. Retry/cancel eligibility is defined in the sending reference.
|
|
91
215
|
|
|
@@ -23,7 +23,9 @@ import { mailConfig } from '@nocobase/app-plugin-mail/server';
|
|
|
23
23
|
export default mailConfig;
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Register this factory under `mail` in `server/config/index.ts` through `defaultAppConfigs()`. The Mail plugin falls back to built-in defaults when the whole namespace is absent, but provider instances still require this registration. `mailConfig` already declares all seven `MAIL_*` environment overrides using section-relative paths, and `pnpm nocobase config env` discovers them. No application-level environment file or duplicated mappings are needed. `mailEnvironmentMappings` remains a compatibility export with fully prefixed `mail.*` paths for older application-level providers; never pass it directly as a section's `env`.
|
|
27
|
+
|
|
28
|
+
Environment values override configuration files and code defaults. Before upgrading, review existing OAuth, sync, webhook and jobs variables that may previously have been inert. Undefined values keep defaults; empty or invalid integer values fail configuration loading, and Mail retains its numeric bounds. Existing application-level providers can coexist when their mappings match; section rules take priority over their custom transformations, so migrate custom parsers into the section rules when needed. A variable mapped to different paths in different sections is rejected even when unset. Mail configuration is not public browser configuration, and webhook secrets must not be exposed.
|
|
27
29
|
|
|
28
30
|
`mail.providers` is a map keyed by stable instance names. The map key is the provider name; each value contains `type` and provider-specific options. Multiple instances of the same type can coexist. Renaming an instance breaks its existing account association. `enabled` defaults to `true`; disabling an instance also makes it unavailable to existing accounts.
|
|
29
31
|
|
|
@@ -94,7 +96,9 @@ Official references: [Gmail API quotas and quota-increase guidance](https://deve
|
|
|
94
96
|
|
|
95
97
|
For IMAP/SMTP, `secure: true` means TLS from connection start; STARTTLS endpoints use the service's prescribed settings. TLS certificate verification defaults to enabled. Mail validates both endpoints before saving account credentials. Use the provider-required app password or authorization code where applicable.
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
The connection form's Username is the shared IMAP and SMTP login name, not the sender's display name. It is optional: an omitted, empty, or whitespace-only value uses the entered mailbox address. A supplied login name is trimmed without changing case or requiring email syntax. Mailbox address and password remain required.
|
|
100
|
+
|
|
101
|
+
Provider secrets, endpoints and allowed OAuth `scopes` live in `mail.providers`. The top-level `MAIL_*` overrides below do not create per-provider credential environment mappings. Do not assume generic `${NAME}` interpolation in YAML; use the target application's supported server configuration mechanism. Keep actual secrets in private server configuration, out of committed examples and client bundles, and restart after changing configuration.
|
|
98
102
|
|
|
99
103
|
## Connect and manage accounts
|
|
100
104
|
|
|
@@ -114,7 +118,9 @@ https://mail.example.com/main/mail/oauth/callback
|
|
|
114
118
|
|
|
115
119
|
Override through `mail.oauthCallbackUrl` or `MAIL_OAUTH_CALLBACK_URL`. A relative path receives the application prefix; an absolute HTTP(S) URL must already include it and reach the mounted callback. Register the exact resulting URL with the OAuth provider. Fragments are invalid. `oauthCallbackUrl` controls only the provider callback endpoint, not the page shown in the browser after authorization.
|
|
116
120
|
|
|
117
|
-
Configure the browser destination through `mail.oauthReturnUrl` or `MAIL_OAUTH_RETURN_URL`. It defaults to
|
|
121
|
+
Configure the browser destination through `mail.oauthReturnUrl` or `MAIL_OAUTH_RETURN_URL`. It defaults to the application's root (`/`), and the plugin registers no built-in Dev, settings or management pages or routes. Register an application-owned page rendering the public `MailAccountsPage`, such as `/mail/accounts`, and configure the return URL to match that personal account page. A relative return path receives the application public base path, so `/mail/accounts` under `/main` becomes `/main/mail/accounts`; an absolute HTTP(S) URL is also supported and is treated as trusted server configuration. Fragments are invalid.
|
|
122
|
+
|
|
123
|
+
Mail warns once at production startup if the effective return destination targets the legacy `/dev/mail/accounts` page, including prefixed and absolute URL forms with query parameters. The current root (`/`) default does not trigger the warning. The warning does not change defaults, block startup, validate arbitrary client route existence, or expose full URLs or secrets. Production apps must register their own authenticated personal account page, such as `/mail/accounts`, with `page / mail.workspace / access` permission; reuse the public `MailAccountsPage`, which already reads authorization results and refreshes accounts. An application-owned administrator overview using the read-only all-user APIs is not a personal connection destination. See [Client integration](client-integration.md) for route composition. Set `MailWorkspacePage.accountsHref` separately: a navigation link does not configure the server's OAuth return.
|
|
118
124
|
|
|
119
125
|
For a production application, configure the callback and return paths together so the provider callback and application page are not confused:
|
|
120
126
|
|
|
@@ -126,7 +132,7 @@ mail:
|
|
|
126
132
|
|
|
127
133
|
With origin `https://mail.example.com` and public base path `/main`, the provider must redirect to `https://mail.example.com/main/mail/oauth/callback`, and the browser will finish at `https://mail.example.com/main/mail/accounts?mailAuthorization=success` or `https://mail.example.com/main/mail/accounts?mailAuthorization=failure`. Register the full callback URL with the selected OAuth provider, such as Google for Gmail or Microsoft Entra ID for Microsoft 365, not the return page URL.
|
|
128
134
|
|
|
129
|
-
When deployment values come from environment variables, use `MAIL_OAUTH_CALLBACK_URL=/mail/oauth/callback` and `MAIL_OAUTH_RETURN_URL=/mail/accounts`
|
|
135
|
+
When deployment values come from environment variables, use `MAIL_OAUTH_CALLBACK_URL=/mail/oauth/callback` and `MAIL_OAUTH_RETURN_URL=/mail/accounts` without repeating `/main`. Composing `mailConfig` as `mail` activates these overrides automatically. Current templates use configuration-section rules, not an application-level environment file. A wrapper that customizes defaults must explicitly retain `env: mailConfig.rules?.env`; merely calling `mailConfig(runtime)` returns defaults, not its environment rules. To customize parsing, spread those section rules and override the selected variable using a section-relative path. Do not pass the legacy full-path `mailEnvironmentMappings` as a section's `env`.
|
|
130
136
|
|
|
131
137
|
The plugin preserves query parameters already present in the configured return URL and adds or replaces `mailAuthorization=success` or `mailAuthorization=failure`. The application-owned return page can use this status to show a result and refresh its account state.
|
|
132
138
|
|
|
@@ -77,24 +77,32 @@ Mail publishes the user-scoped realtime topic `mail:messages` with `{ kind: 'mai
|
|
|
77
77
|
|
|
78
78
|
## Messages, conversations, attachments, and sending
|
|
79
79
|
|
|
80
|
-
| Method | Path | Request / result
|
|
81
|
-
| -------- | -------------------------------------------------------------------- |
|
|
82
|
-
| `GET` | `/messages` | Filters by `accountId`, `folderId`, `labelId`, `conversationId`, `q`, `unread`, and `starred`; pages with `pageSize` and `pageToken`. |
|
|
83
|
-
| `GET` | `/messages/countUnread` | Returns the current user's unread count as a number.
|
|
84
|
-
| `POST` | `/messages/send` | Accepts `MailComposeInput`; returns `MailSubmissionView`.
|
|
85
|
-
| `POST` | `/messages/sendBulk` | Accepts `MailBulkComposeInput`; returns `MailSubmissionView[]`.
|
|
86
|
-
| `POST` | `/messages/saveDraft` | Accepts draft compose fields; creates the draft or replaces the one named by `draftMessageId` or `draftKey`; returns `MailMessage`.
|
|
87
|
-
| `GET` | `/accounts/:accountId/conversations/:conversationId/messages` | Conversation messages, paged with `pageSize` and `pageToken`.
|
|
88
|
-
| `GET` | `/accounts/:accountId/messages/:messageId` | Returns a `MailMessage`.
|
|
89
|
-
| `PATCH` | `/accounts/:accountId/messages/:messageId` | Optional read/starred/note/todo changes; returns the updated `MailMessage`.
|
|
90
|
-
| `DELETE` | `/accounts/:accountId/messages/:messageId` | Optional `permanently=true` query parameter; returns `204`.
|
|
91
|
-
| `GET` | `/accounts/:accountId/messages/:messageId/attachments/:attachmentId` | Streams a personal mailbox attachment.
|
|
92
|
-
| `POST` | `/accounts/:accountId/messages/:messageId/modifyLabels` | `addLabelIds` and/or `removeLabelIds`; returns the updated `MailMessage`.
|
|
93
|
-
| `POST` | `/accounts/:accountId/messages/:messageId/move` | Requires `providerFolderId`; returns the updated `MailMessage`.
|
|
94
|
-
| `POST` | `/accounts/:accountId/messages/:messageId/retryContent` | Retries deferred message content retrieval; returns the updated `MailMessage`.
|
|
95
|
-
| `POST` | `/accounts/:accountId/messages/:messageId/resolveDraftConflict` | Requires `action` `useRemote` or `keepLocal`; returns the resolved `MailMessage`.
|
|
96
|
-
| `POST` | `/attachments` | `multipart/form-data` with field `file`, at most 27 MiB; returns `201` and `MailOutboundAttachmentView`.
|
|
97
|
-
| `GET` | `/attachments/:attachmentId` | Streams an uploaded outbound attachment.
|
|
80
|
+
| Method | Path | Request / result |
|
|
81
|
+
| -------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| `GET` | `/messages` | Filters by `accountId`, `folderId`, `labelId`, `conversationId`, `q`, `participant`, `unread`, and `starred`; pages with `pageSize` and `pageToken`. |
|
|
83
|
+
| `GET` | `/messages/countUnread` | Returns the current user's unread count as a number. |
|
|
84
|
+
| `POST` | `/messages/send` | Accepts `MailComposeInput`; returns `MailSubmissionView`. |
|
|
85
|
+
| `POST` | `/messages/sendBulk` | Accepts `MailBulkComposeInput`; returns `MailSubmissionView[]`. |
|
|
86
|
+
| `POST` | `/messages/saveDraft` | Accepts draft compose fields; creates the draft or replaces the one named by `draftMessageId` or `draftKey`; returns `MailMessage`. |
|
|
87
|
+
| `GET` | `/accounts/:accountId/conversations/:conversationId/messages` | Conversation messages, paged with `pageSize` and `pageToken`. |
|
|
88
|
+
| `GET` | `/accounts/:accountId/messages/:messageId` | Returns a `MailMessage`. |
|
|
89
|
+
| `PATCH` | `/accounts/:accountId/messages/:messageId` | Optional read/starred/note/todo changes; returns the updated `MailMessage`. |
|
|
90
|
+
| `DELETE` | `/accounts/:accountId/messages/:messageId` | Optional `permanently=true` query parameter; returns `204`. |
|
|
91
|
+
| `GET` | `/accounts/:accountId/messages/:messageId/attachments/:attachmentId` | Streams a personal mailbox attachment. |
|
|
92
|
+
| `POST` | `/accounts/:accountId/messages/:messageId/modifyLabels` | `addLabelIds` and/or `removeLabelIds`; returns the updated `MailMessage`. |
|
|
93
|
+
| `POST` | `/accounts/:accountId/messages/:messageId/move` | Requires `providerFolderId`; returns the updated `MailMessage`. |
|
|
94
|
+
| `POST` | `/accounts/:accountId/messages/:messageId/retryContent` | Retries deferred message content retrieval; returns the updated `MailMessage`. |
|
|
95
|
+
| `POST` | `/accounts/:accountId/messages/:messageId/resolveDraftConflict` | Requires `action` `useRemote` or `keepLocal`; returns the resolved `MailMessage`. |
|
|
96
|
+
| `POST` | `/attachments` | `multipart/form-data` with field `file`, at most 27 MiB; returns `201` and `MailOutboundAttachmentView`. |
|
|
97
|
+
| `GET` | `/attachments/:attachmentId` | Streams an uploaded outbound attachment. |
|
|
98
|
+
|
|
99
|
+
### Participant query semantics
|
|
100
|
+
|
|
101
|
+
`GET /messages` and `GET /management/messages` accept optional `participant` as a full mailbox (`alice@example.com`) or an exact domain (`@example.com`). The server trims leading/trailing whitespace and lowercases the whole mailbox/domain. Supported input is an ASCII dot-atom local part (at most 64 characters) with a dotted DNS domain (at most 253 characters, labels at most 63); the query value is at most 320 characters. Display-name forms, quoted local parts, Unicode addresses and malformed/empty values are rejected with `400 INVALID_ARGUMENT`, reason `INVALID_INPUT`, and a `participant` field violation. Permission is checked first. Omit the parameter to disable it.
|
|
102
|
+
|
|
103
|
+
Only actual From, To and Cc addresses match. Subject, preview, body, display names, Bcc and Reply-To are excluded; aliases and `+tag` addresses are not folded. `@example.com` does not match `sub.example.com`, `notexample.com` or `example.com.evil`. Existing `q` fuzzy search is unchanged (including display names and Bcc) and intersects with participant and all other filters. Ownership, management permission, sort order and IMAP-copy record granularity are unchanged. The predicate applies before totals and pagination and never duplicates a matching message because several participants match. Searches cover locally synchronized mail only.
|
|
104
|
+
|
|
105
|
+
Conversation-message detail and full-thread counts retain their existing semantics: a filtered list row can open a complete thread containing other participants. The internal participant index has no public CRUD endpoint. Existing installations must finish the [maintenance-window backfill](synchronization-and-diagnostics.md#participant-index-upgrade) before relying on historical filtered results.
|
|
98
106
|
|
|
99
107
|
## Synchronization and delivery history
|
|
100
108
|
|
|
@@ -111,12 +119,12 @@ Mail publishes the user-scoped realtime topic `mail:messages` with `{ kind: 'mai
|
|
|
111
119
|
|
|
112
120
|
## All-user management
|
|
113
121
|
|
|
114
|
-
| Method | Path | Request / result
|
|
115
|
-
| ------ | ------------------------------------------------------------------------------- |
|
|
116
|
-
| `GET` | `/management/messages` | Filters by `accountId`, `folderId`, `q`, `unread`, and `starred`; paged with `page` and `pageSize`. |
|
|
117
|
-
| `POST` | `/management/messages/batchApply` | Accepts `MailManagementMessageActionInput`; returns per-item results.
|
|
118
|
-
| `GET` | `/management/accounts/:accountId/messages/:messageId` | Returns a read-only `MailMessage`; does not mark it read.
|
|
119
|
-
| `GET` | `/management/accounts/:accountId/messages/:messageId/attachments/:attachmentId` | Streams an all-user management attachment.
|
|
122
|
+
| Method | Path | Request / result |
|
|
123
|
+
| ------ | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
124
|
+
| `GET` | `/management/messages` | Filters by `accountId`, `folderId`, `q`, `participant`, `unread`, and `starred`; paged with `page` and `pageSize`. |
|
|
125
|
+
| `POST` | `/management/messages/batchApply` | Accepts `MailManagementMessageActionInput`; returns per-item results. |
|
|
126
|
+
| `GET` | `/management/accounts/:accountId/messages/:messageId` | Returns a read-only `MailMessage`; does not mark it read. |
|
|
127
|
+
| `GET` | `/management/accounts/:accountId/messages/:messageId/attachments/:attachmentId` | Streams an all-user management attachment. |
|
|
120
128
|
|
|
121
129
|
## OAuth and Provider webhooks
|
|
122
130
|
|
|
@@ -16,7 +16,7 @@ The package root is a Server alias retained for plugin registration and applicat
|
|
|
16
16
|
|
|
17
17
|
### Plugin and configuration
|
|
18
18
|
|
|
19
|
-
The Server entry exports the default plugin, `mailConfig`, `DEFAULT_MAIL_CONFIG`, `mailEnvironmentMappings`, `resolveMailConfig`, and the `MailConfig`/provider configuration types. Applications register the plugin and explicitly compose its configuration factory as described in [Configuration and accounts](configuration-and-accounts.md).
|
|
19
|
+
The Server entry exports the default plugin, `mailConfig`, `DEFAULT_MAIL_CONFIG`, `mailEnvironmentMappings`, `resolveMailConfig`, and the `MailConfig`/provider configuration types. Applications register the plugin and explicitly compose its configuration factory as described in [Configuration and accounts](configuration-and-accounts.md). `mailConfig.rules.env` supplies seven section-relative `MAIL_*` mappings; wrappers must retain those rules explicitly. `mailEnvironmentMappings` preserves fully prefixed `mail.*` paths and parsing for legacy application-level environment providers, not section-level `env`. Neither interface makes private Mail configuration public.
|
|
20
20
|
|
|
21
21
|
### Supported tokens
|
|
22
22
|
|
|
@@ -62,10 +62,13 @@ Output status, folder-type and Provider error category/reason types have `Known.
|
|
|
62
62
|
### Composition and data access
|
|
63
63
|
|
|
64
64
|
- The default export registers the Mail Client plugin.
|
|
65
|
-
- `MailWorkspacePage` and `MailWorkspacePageProps` provide the full workspace. `
|
|
65
|
+
- `MailWorkspacePage` and `MailWorkspacePageProps` provide the full workspace. `title` and `description` customize the heading; `templateVariables` supplies an allowlisted record context for saved template placeholders and does not associate messages with application records. `accountsHref` is an optional application-internal personal account path without a deployment prefix: the workspace resolves it with `resolveAppUrl()`, shows a connection link when no usable accounts exist, and a Mail accounts link in its header otherwise. Without it no account link is generated. `headerActions?: ReactNode` appends actions after Compose, Sync and the optional account link without replacing them.
|
|
66
|
+
- `MailAccountsPage` and `MailAccountsPageProps` provide the personal connection and management page for application-owned production routes. The administrator's read-only account overview is not a replacement.
|
|
66
67
|
- `MailClient` and `mailClientToken` expose the typed client data layer. Resolve the app-owned instance with `useMailClient()` in React or `app.services.resolve(mailClientToken)` elsewhere; do not construct a separate `MailClient` for normal UI integration.
|
|
68
|
+
- `useMailUnreadCount(): MailUnreadCountState` exposes the current user's count, loading state and latest request error through the host Mail/realtime services. Until the first successful response, `unreadCount` is `undefined`, not zero; refresh failures retain the last successful count. It shares the navigation icon's local/realtime/reconnect/focus/polling refresh logic and instance-local debounce/serialization. Mount in a session-owned scope that remounts on user changes; there is no shared global cache or automatic session observer. See [custom unread menus](client-integration.md#unread-counts-on-custom-menus). Low-level event/subscription implementation files remain private.
|
|
67
69
|
- `mailErrorMessage(cause, fallback)` returns the text to show for a failed call: the server's `localizedMessage`, or `fallback`. Code that must react to a specific failure branches on `ApiClientError.reason` from `@nocobase/app-client` instead.
|
|
68
70
|
- `renderMailTemplate()`, `plainTextToMailHtml()`, and `htmlToPlainText()` are supported template/body helpers. `MailTemplateVariables` and `RenderedMailTemplate` describe their data.
|
|
71
|
+
- `prepareMailReply(mail, message): Promise<MailComposerRequest>` prepares a browser-side reply using the original account/local message ID, Reply-To or From, and a single `Re:` prefix. It retries incomplete content and copies referenced CID images into owned `uploads`, rejects preparation failures, and does not carry ordinary attachments, open a composer, save or send mail. The returned request preserves the separate reply quote for shared composer sending and draft recovery. `MailComposerRequest` is exported here and from `/client/components`. See [custom reply integration](client-integration.md#prepare-replies-on-custom-pages) for network effects, sandbox rendering and caller-owned concurrency guards.
|
|
69
72
|
- `MAIL_PLUGIN_NS`, `MAIL_REALTIME_TOPIC`, and `MailRealtimeEvent` expose the translation namespace and invalidation event contract.
|
|
70
73
|
- `MAIL_VIRTUAL_FOLDER_IDS` provides stable query identifiers for built-in cross-account folders. `MAIL_LABEL_COLORS` and `DEFAULT_MAIL_LABEL_COLOR` provide the supported user-label palette and default.
|
|
71
74
|
|
|
@@ -81,33 +84,37 @@ Output status, folder-type and Provider error category/reason types have `Known.
|
|
|
81
84
|
- **Synchronization:** `startSync`, `getSyncRun`, `listSyncRunsPage`, `retrySyncRun`, `cancelSyncRun`.
|
|
82
85
|
- **Delivery history:** `listSubmissionsPage`, `retrySubmission`, `cancelSubmission`.
|
|
83
86
|
|
|
87
|
+
`MailMessagesQuery` and `MailManagedMessagesQuery` include an optional `participant: string` for an exact normalized From/To/Cc mailbox or `@domain` match, independent of `q`. `MailClient` forwards it unchanged; omit it to clear the filter. List filtering does not trim full conversation details or change full-thread counts. See [CRM correspondence](client-integration.md#crm-correspondence-and-participant-filtering) for examples, validation and normalization boundaries.
|
|
88
|
+
|
|
84
89
|
The TypeScript signatures are the request/result source of truth. For direct HTTP clients, see [HTTP API](http-api.md).
|
|
85
90
|
|
|
86
91
|
## Reusable components
|
|
87
92
|
|
|
88
93
|
All components below are exported from `@nocobase/app-plugin-mail/client/components`. Components that call `useMailClient()` require the Mail Client plugin to be registered in the host application.
|
|
89
94
|
|
|
90
|
-
| Component | Exported props and related types
|
|
91
|
-
| ----------------------------------- |
|
|
92
|
-
| `MailAccountCard` | `MailAccountCardProps`
|
|
93
|
-
| `MailAccountConnector` | `MailAccountConnectorProps`, `MailAccountConnectorLabels`, `MailAccountCredentials`
|
|
94
|
-
| `MailConversationView` | `MailConversationViewProps`, `MailConversationViewLabels`
|
|
95
|
-
| `MailMessageList` | `MailMessageListProps`, `MailMessageListLabels`
|
|
96
|
-
| `MailProviderCard` | `MailProviderCardProps`
|
|
97
|
-
| `MailRichTextEditor` | `MailRichTextEditorProps`, `MailRichTextValue`, `MailRichTextEditorLabels`, `MailRichTextEditorInsertOption`, `MailRichTextEditorInsertMenu`, `MailRichTextEditorInsertActions`
|
|
98
|
-
| `MailNavigationIcon` | No props; displays the user's unread count using the host app services.
|
|
99
|
-
| `MailLabelManager` | No props; reads and edits the current user's labels.
|
|
100
|
-
| `MailLabelTag`, `MailLabelColorDot` | `MailLabelTagProps`, `MailLabelColorDotProps`
|
|
101
|
-
| `MailSignatureManager` | `MailSignatureManagerProps`
|
|
102
|
-
| `MailStatusBadge` | `MailStatusBadgeProps`, `MailStatusTone`
|
|
103
|
-
| `MailSyncPolicyFields` | `MailSyncPolicyFieldsProps`, `MailSyncPolicyValue`
|
|
104
|
-
| `MailTemplateManager` | No props; reads and edits the current user's templates.
|
|
105
|
-
| `MailboxSidebar` | `MailboxSidebarProps`, `MailboxSidebarLabels`, `MailboxFolderGroup`, `MailboxSmartView`
|
|
106
|
-
| `MailComposer` | `MailComposerComponentProps` and shared `MailComposerProps`, `MailComposerRequest`, `MailComposerState`, `MailComposerCompletionResult`, `MailForwardQuote`, `MailTemplateVariables`, `EMPTY_MAIL_COMPOSER` |
|
|
107
|
-
| `MailWorkspaceComposer` | `MailWorkspaceComposerProps` and the shared composer contracts above.
|
|
95
|
+
| Component | Exported props and related types |
|
|
96
|
+
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
97
|
+
| `MailAccountCard` | `MailAccountCardProps` |
|
|
98
|
+
| `MailAccountConnector` | `MailAccountConnectorProps`, `MailAccountConnectorLabels`, `MailAccountCredentials` |
|
|
99
|
+
| `MailConversationView` | `MailConversationViewProps`, `MailConversationViewLabels` |
|
|
100
|
+
| `MailMessageList` | `MailMessageListProps`, `MailMessageListLabels` |
|
|
101
|
+
| `MailProviderCard` | `MailProviderCardProps` |
|
|
102
|
+
| `MailRichTextEditor` | `MailRichTextEditorProps`, `MailRichTextValue`, `MailRichTextEditorLabels`, `MailRichTextEditorInsertOption`, `MailRichTextEditorInsertMenu`, `MailRichTextEditorInsertActions` |
|
|
103
|
+
| `MailNavigationIcon` | No props; displays the user's unread count using the host app services. |
|
|
104
|
+
| `MailLabelManager` | No props; reads and edits the current user's labels. |
|
|
105
|
+
| `MailLabelTag`, `MailLabelColorDot` | `MailLabelTagProps`, `MailLabelColorDotProps` |
|
|
106
|
+
| `MailSignatureManager` | `MailSignatureManagerProps` |
|
|
107
|
+
| `MailStatusBadge` | `MailStatusBadgeProps`, `MailStatusTone` |
|
|
108
|
+
| `MailSyncPolicyFields` | `MailSyncPolicyFieldsProps`, `MailSyncPolicyValue` |
|
|
109
|
+
| `MailTemplateManager` | No props; reads and edits the current user's templates. |
|
|
110
|
+
| `MailboxSidebar` | `MailboxSidebarProps`, `MailboxSidebarLabels`, `MailboxFolderGroup`, `MailboxSmartView` |
|
|
111
|
+
| `MailComposer` | `MailComposerComponentProps` and shared `MailComposerProps`, `MailComposerRequest`, `MailComposerState`, `MailComposerCompletionResult`, `MailComposerCompletionCallback`, `MailComposerCompletionDetails`, `MailComposerSubmissionSnapshot`, `MailForwardQuote`, `MailTemplateVariables`, `EMPTY_MAIL_COMPOSER` |
|
|
112
|
+
| `MailWorkspaceComposer` | `MailWorkspaceComposerProps` and the shared composer contracts above. |
|
|
108
113
|
|
|
109
114
|
`MailComposer` renders one supplied account/request. `MailWorkspaceComposer` also manages account and sender identity selection across eligible accounts. Completion results are open strings; callers should handle known results and provide a fallback for future outcomes.
|
|
110
115
|
|
|
116
|
+
The shared `onComplete(result, rejectedRecipients?, error?, details?)` uses `MailComposerCompletionCallback` to support synchronous and asynchronous completion while retaining legacy return-discarding callbacks. Its optional fourth argument is discriminated by `kind`: `normal` or `bulk` includes the detached actual client request and every returned `MailSubmissionView`, while `draft` contains only a separate draft reference and no submissions. A response-less unknown outcome has no submission IDs. The first three arguments retain their existing summary semantics, not delivery guarantees. Optional `onCompletionError` isolates callback throws/rejections without delaying closing or retrying mail. See [business-record completion](client-integration.md#associate-composer-completion-with-business-records) for idempotent integration and snapshot boundaries.
|
|
117
|
+
|
|
111
118
|
## HTTP and realtime
|
|
112
119
|
|
|
113
120
|
The authenticated `/api/mail` routes, payloads, paging, permission resources, error reasons, OAuth callback and Provider webhook are documented in [HTTP API](http-api.md). Prefer `MailClient` for application code. The `mail:messages` realtime event is a user-scoped invalidation signal with no message body; refresh affected Mail data after receiving it or reconnecting.
|
|
@@ -8,6 +8,20 @@ Automatic synchronization is configured globally through `mail.automaticSyncInte
|
|
|
8
8
|
|
|
9
9
|
`mail.syncBatchSize` or `MAIL_SYNC_BATCH_SIZE` defaults to 100, with an integer range of 1–200. This bounds each provider list page, not the total imported history. The account screen defaults a new initial sync to one calendar month ago. Direct account-creation APIs require `initialSyncReceivedAfter`; direct initial-sync requests must carry `receivedAfter` unless the account already has a saved start date. Both are RFC 3339 date-times such as `2026-01-01T00:00:00Z`. Legacy accounts without one use a one-month boundary for automatic or push-triggered first syncs. An active pre-upgrade initial run missing a valid boundary restarts with the same one-month fallback; Gmail rejects any initial list call without a valid date. The sync request no longer accepts `maxMessages`; an import is bounded only by its start date.
|
|
10
10
|
|
|
11
|
+
## Participant index upgrade
|
|
12
|
+
|
|
13
|
+
The participant feature adds an internal `mailMessageParticipants` collection and indexes, with one derived row per normalized From/To/Cc role/address. Historical data is backfilled from stored sender/recipient JSON, without provider requests or changes to original message JSON, timestamps, bodies or folders. Unsupported or invalid historical addresses/JSON are skipped with non-sensitive diagnostic counts; valid addresses from the same message remain eligible. Do not edit the index directly or treat partial backfill results as complete.
|
|
14
|
+
|
|
15
|
+
Before production deployment, measure representative and large mailboxes: message and participant row counts, migration time, index storage, write overhead, locks and transaction-log usage. Large To/Cc sets increase participant rows and insertion cost. Keyset reads and bounded insert batches constrain memory and binding parameters, not total transaction duration or lock time; the migration is not an online backfill. If measurements do not fit a maintenance window, stop and plan a separately designed resumable task with completion and concurrency guarantees.
|
|
16
|
+
|
|
17
|
+
1. With explicit production authorization, take and verify a restorable database backup and schedule the measured maintenance window.
|
|
18
|
+
2. Stop application writes and all Mail/background sync workers, including other processes sharing the database; do not run the old version concurrently with backfill.
|
|
19
|
+
3. Deploy the upgraded artifacts and run the application's migration command (`pnpm nocobase db apply` in a source application, or `node dist/cli/index.js db apply` in a deployment). This applies the schema and historical backfill together.
|
|
20
|
+
4. Verify successful migration completion, inspect non-sensitive skip counts, and check known historical From/To/Cc full-address and `@domain` queries, exclusions, totals and pagination. Verify original messages are intact before enabling traffic.
|
|
21
|
+
5. Only then restart the upgraded application and sync workers and verify newly synchronized messages appear in participant queries.
|
|
22
|
+
|
|
23
|
+
On failure, leave writes and synchronization stopped, inspect the migration error and transaction outcome, and use the application's migration recovery procedures or the verified backup before retrying. Never expose a partly populated index or repair data by editing Mail tables. A migration rollback removes only the derived participant structure; original sender/recipient JSON remains available for a later reapply/backfill. Rehearse apply/rollback/reapply and failure recovery on a database copy before the production window.
|
|
24
|
+
|
|
11
25
|
## Gmail quota, HTTP batch, and retry behavior
|
|
12
26
|
|
|
13
27
|
For Gmail, one list page is fetched with `messages.list`, followed by one `messages.get` per message. Mail groups up to 10 message gets into each HTTP `multipart/mixed` batch request. Batching reduces HTTP connections only: Google charges every embedded call separately, so `messages.get` still costs 20 quota units and `messages.list` costs 5. A 100-message page therefore costs about 2,005 units before incidental profile, label, history, or fallback metadata calls; it does not become a single quota unit. See [Gmail quota configuration](configuration-and-accounts.md#gmail-quota-configuration) for defaults and project overrides.
|