-
+
+
@@ -649,7 +664,7 @@ onBeforeUnmount(() => {
diff --git a/frontend/src/components/MailContentRenderer.vue b/frontend/src/components/MailContentRenderer.vue
index 5922a58..3dc1351 100644
--- a/frontend/src/components/MailContentRenderer.vue
+++ b/frontend/src/components/MailContentRenderer.vue
@@ -34,7 +34,7 @@ const props = defineProps({
type: Boolean,
default: false
},
- enableReadStatus: {
+ enableMailStates: {
type: Boolean,
default: false
},
@@ -154,7 +154,7 @@ const handleSaveToS3 = async (filename, blob) => {
{{ t('downloadMail') }}
-
+
{{ mail.unread ? t('markRead') : t('markUnread') }}
diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js
index 95e4b22..dd0144a 100644
--- a/frontend/src/store/index.js
+++ b/frontend/src/store/index.js
@@ -24,7 +24,7 @@ export const useGlobalState = createGlobalState(
disableAnonymousUserCreateEmail: false,
disableCustomAddressName: false,
enableUserDeleteEmail: false,
- enableReadStatus: false,
+ enableMailStates: false,
enableAutoReply: false,
enableIndexAbout: false,
/** @type {string[]} */
diff --git a/frontend/src/views/Index.vue b/frontend/src/views/Index.vue
index 2dcf866..68c7127 100644
--- a/frontend/src/views/Index.vue
+++ b/frontend/src/views/Index.vue
@@ -32,15 +32,15 @@ const SendMail = defineAsyncComponent(() => {
const { t } = useScopedI18n('views.Index')
-const fetchMailData = async (limit, offset, readStatus = 'all') => {
+const fetchMailData = async (limit, offset, mailState) => {
if (mailIdQuery.value > 0) {
const singleMail = await api.fetch(`/api/mail/${mailIdQuery.value}`);
if (singleMail) return { results: [singleMail], count: 1 };
return { results: [], count: 0 };
}
- const readStatusQuery = readStatus === 'all' ? '' : `&read_status=${readStatus}`
+ const mailStateQuery = mailState ? `&mail_state=${encodeURIComponent(mailState)}` : ''
return await api.fetch(
- `/api/mails?limit=${limit}&offset=${offset}${readStatusQuery}`
+ `/api/mails?limit=${limit}&offset=${offset}${mailStateQuery}`
);
};
@@ -48,13 +48,17 @@ const deleteMail = async (curMailId) => {
await api.fetch(`/api/mails/${curMailId}`, { method: 'DELETE' });
};
-const updateMailReadStatus = async (ids, action) => {
- return await api.fetch(`/api/mails/read-status`, {
+const updateMailState = async (ids, state) => {
+ return await api.fetch(`/api/mails/state`, {
method: 'PATCH',
- body: JSON.stringify({ ids, action })
+ body: JSON.stringify({ ids, state })
});
};
+const fetchMailStates = async () => {
+ return await api.fetch(`/api/mail-states`)
+}
+
const deleteSenboxMail = async (curMailId) => {
await api.fetch(`/api/sendbox/${curMailId}`, { method: 'DELETE' });
};
@@ -138,7 +142,8 @@ onMounted(() => {
+ :enableMailStates="openSettings.enableMailStates" :updateMailState="updateMailState"
+ :fetchMailStates="fetchMailStates" />
{
+ return mailStates.value.find(state => state.unread === unread)?.value
+}
+
// 复制地址
const copyAddress = async () => {
try {
@@ -50,10 +55,12 @@ const fetchMails = async () => {
totalCount.value = count > 0 ? count : totalCount.value;
const rawMail = results && results.length > 0 ? results[0] : null
currentMail.value = rawMail ? await processItem(rawMail) : null
- if (openSettings.value.enableReadStatus && rawMail) {
- const response = await api.fetch(`/api/mails/read-status`, {
+ if (openSettings.value.enableMailStates && rawMail) {
+ const state = getReadStateValue(false)
+ if (!state) return
+ const response = await api.fetch(`/api/mails/state`, {
method: 'PATCH',
- body: JSON.stringify({ ids: [rawMail.id], action: 'read' })
+ body: JSON.stringify({ ids: [rawMail.id], state })
})
const updatedMail = response.results?.[0]
if (updatedMail) currentMail.value.unread = updatedMail.unread
@@ -65,13 +72,15 @@ const fetchMails = async () => {
}
const toggleCurrentMailUnread = async () => {
- if (!currentMail.value || !openSettings.value.enableReadStatus) return
+ if (!currentMail.value || !openSettings.value.enableMailStates) return
try {
- const response = await api.fetch(`/api/mails/read-status`, {
+ const state = getReadStateValue(!currentMail.value.unread)
+ if (!state) return
+ const response = await api.fetch(`/api/mails/state`, {
method: 'PATCH',
body: JSON.stringify({
ids: [currentMail.value.id],
- action: 'toggle',
+ state,
})
})
const updatedMail = response.results?.[0]
@@ -131,6 +140,10 @@ watch(currentPage, () => {
onMounted(async () => {
await api.getSettings()
+ if (openSettings.value.enableMailStates) {
+ const { results = [] } = await api.fetch(`/api/mail-states`)
+ mailStates.value = results
+ }
await fetchMails()
// 启动自动刷新
@@ -245,7 +258,7 @@ onBeforeUnmount(() => {
diff --git a/frontend/src/views/user/UserMailBox.vue b/frontend/src/views/user/UserMailBox.vue
index 645cabd..a92403a 100644
--- a/frontend/src/views/user/UserMailBox.vue
+++ b/frontend/src/views/user/UserMailBox.vue
@@ -20,12 +20,12 @@ const queryMail = () => {
mailBoxKey.value = Date.now();
}
-const fetchMailData = async (limit, offset, readStatus = 'all') => {
+const fetchMailData = async (limit, offset, mailState) => {
return await api.fetch(
`/user_api/mails`
+ `?limit=${limit}`
+ `&offset=${offset}`
- + (readStatus === 'all' ? '' : `&read_status=${readStatus}`)
+ + (mailState ? `&mail_state=${encodeURIComponent(mailState)}` : '')
+ (addressFilter.value ? `&address=${addressFilter.value}` : '')
);
}
@@ -51,13 +51,17 @@ const deleteMail = async (curMailId) => {
await api.fetch(`/user_api/mails/${curMailId}`, { method: 'DELETE' });
};
-const updateMailReadStatus = async (ids, action) => {
- return await api.fetch(`/user_api/mails/read-status`, {
+const updateMailState = async (ids, state) => {
+ return await api.fetch(`/user_api/mails/state`, {
method: 'PATCH',
- body: JSON.stringify({ ids, action })
+ body: JSON.stringify({ ids, state })
});
};
+const fetchMailStates = async () => {
+ return await api.fetch(`/user_api/mail-states`)
+}
+
watch(addressFilter, async (newValue) => {
queryMail();
});
@@ -78,7 +82,7 @@ onMounted(() => {
+ :deleteMail="deleteMail" :showFilterInput="true" :enableMailStates="openSettings.enableMailStates"
+ :updateMailState="updateMailState" :fetchMailStates="fetchMailStates" />
diff --git a/vitepress-docs/docs/en/guide/feature/mail-api.md b/vitepress-docs/docs/en/guide/feature/mail-api.md
index 3341e4d..532750b 100644
--- a/vitepress-docs/docs/en/guide/feature/mail-api.md
+++ b/vitepress-docs/docs/en/guide/feature/mail-api.md
@@ -19,29 +19,31 @@ res = requests.get(
**Note**: `/api/mails` returns raw RFC822 data by design (for example `source`/`raw`), and it does not guarantee parsed fields such as `subject`, `text`, or `html`. Parse the raw source on the client side (for example with `mail-parser-wasm` or `postal-mime`) if you need readable message content.
-## Mail Read Status API
+## Mail State API
After enabling `ENABLE_MAIL_FLAGS` and running the database migration, each mail response includes the boolean field `unread`. The backend owns storage, historical `NULL` compatibility, and state calculation.
-With an Address JWT, use `PATCH /api/mails/read-status` to update up to 100 mail IDs. The `action` can be `read`, `unread`, or `toggle`.
+With an Address JWT, use `GET /api/mail-states` to retrieve available states. The backend combines its system-state enum with address-specific custom states. The frontend uses each returned `value` directly for filtering and moving, and displays its `label_key` or `label`.
+
+Use `PATCH /api/mails/state` to move the state of up to 100 mail IDs:
```python
requests.patch(
- "https://