- {{ t('save') }}
+ {{ t('save') }}
diff --git a/vitepress-docs/docs/en/cli.md b/vitepress-docs/docs/en/cli.md
index 04a2e143..9661c3e6 100644
--- a/vitepress-docs/docs/en/cli.md
+++ b/vitepress-docs/docs/en/cli.md
@@ -56,12 +56,16 @@ pnpm run deploy
`wrangler.toml`
-```bash
+```toml
name = "cloudflare_temp_email"
main = "src/worker.js"
compatibility_date = "2023-08-14"
node_compat = true
+# enable cron if you want set auto clean up
+# [triggers]
+# crons = [ "0 0 * * *" ]
+
[vars]
PREFIX = "tmp" # The mailbox name prefix to be processed
# If you want your site to be private, uncomment below and change your password
diff --git a/vitepress-docs/docs/zh/guide/cli/worker.md b/vitepress-docs/docs/zh/guide/cli/worker.md
index ec2426aa..8f927bb2 100644
--- a/vitepress-docs/docs/zh/guide/cli/worker.md
+++ b/vitepress-docs/docs/zh/guide/cli/worker.md
@@ -20,6 +20,10 @@ compatibility_date = "2023-12-01"
# ]
node_compat = true
+# 如果你想要使用定时任务清理邮件,取消下面的注释,并修改 cron 表达式
+# [triggers]
+# crons = [ "0 0 * * *" ]
+
[vars]
PREFIX = "tmp" # 要处理的邮箱名称前缀,不需要后缀可配置为空字符串
# 如果你想要你的网站私有,取消下面的注释,并修改密码
diff --git a/worker/src/admin/cleanup_api.js b/worker/src/admin/cleanup_api.js
new file mode 100644
index 00000000..c0d02ba8
--- /dev/null
+++ b/worker/src/admin/cleanup_api.js
@@ -0,0 +1,25 @@
+import { cleanup } from '../common';
+import { CONSTANTS } from '../constants';
+import { getJsonSetting, saveSetting } from '../utils';
+
+export default {
+ cleanup: async (c) => {
+ const { cleanType, cleanDays } = await c.req.json();
+ try {
+ await cleanup(c, cleanType, cleanDays);
+ } catch (error) {
+ console.error(error);
+ return c.text(`Failed to cleanup ${error.message}`, 500)
+ }
+ return c.json({ success: true })
+ },
+ getCleanup: async (c) => {
+ const value = await getJsonSetting(c, CONSTANTS.AUTO_CLEANUP_KEY);
+ return c.json(value || {})
+ },
+ saveCleanup: async (c) => {
+ const value = await c.req.json();
+ await saveSetting(c, CONSTANTS.AUTO_CLEANUP_KEY, JSON.stringify(value));
+ return c.json({ success: true })
+ }
+}
diff --git a/worker/src/admin_api.js b/worker/src/admin_api.js
index f2208da5..7d1b8c29 100644
--- a/worker/src/admin_api.js
+++ b/worker/src/admin_api.js
@@ -1,8 +1,9 @@
import { Hono } from 'hono'
import { Jwt } from 'hono/utils/jwt'
-import { sendAdminInternalMail } from './utils'
+import { sendAdminInternalMail, getJsonSetting, saveSetting } from './utils'
import { newAddress } from './common'
import { CONSTANTS } from './constants'
+import cleanup_api from './admin/cleanup_api'
const api = new Hono()
@@ -294,49 +295,16 @@ api.get('/admin/statistics', async (c) => {
})
});
-api.post('/admin/cleanup', async (c) => {
- const { cleanType, cleanDays } = await c.req.json();
- if (!cleanType || !cleanDays || cleanDays < 0 || cleanDays > 30) {
- return c.text("Invalid cleanType or cleanDays", 400)
- }
- console.log(`Cleanup ${cleanType} before ${cleanDays} days`);
- switch (cleanType) {
- case "mails":
- await c.env.DB.prepare(`
- DELETE FROM raw_mails WHERE created_at < datetime('now', '-${cleanDays} day')`
- ).run();
- break;
- case "mails_unknow":
- await c.env.DB.prepare(`
- DELETE FROM raw_mails WHERE address NOT IN
- (select name from address) AND created_at < datetime('now', '-${cleanDays} day')`
- ).run();
- break;
- case "address":
- await c.env.DB.prepare(`
- DELETE FROM address WHERE updated_at < datetime('now', '-${cleanDays} day')`
- ).run();
- break;
- case "sendbox":
- await c.env.DB.prepare(`
- DELETE FROM sendbox WHERE created_at < datetime('now', '-${cleanDays} day')`
- ).run();
- break;
- default:
- return c.text("Invalid cleanType", 400)
- }
- return c.json({
- success: true
- })
-})
+api.post('/admin/cleanup', cleanup_api.cleanup)
+api.get('/admin/auto_cleanup', cleanup_api.getCleanup)
+api.post('/admin/auto_cleanup', cleanup_api.saveCleanup)
+
api.get('/admin/account_settings', async (c) => {
try {
- const value = await c.env.DB.prepare(
- `SELECT value FROM settings where key = ?`
- ).bind(CONSTANTS.ADDRESS_BLOCK_LIST_KEY).first("value");
+ const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
return c.json({
- blockList: value ? JSON.parse(value) : []
+ blockList: value || []
})
} catch (error) {
console.error(error);
@@ -349,14 +317,10 @@ api.post('/admin/account_settings', async (c) => {
if (!blockList) {
return c.text("Invalid blockList", 400)
}
- await c.env.DB.prepare(
- `INSERT or REPLACE INTO settings (key, value) VALUES (?, ?)`
- + ` ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')`
- ).bind(
- CONSTANTS.ADDRESS_BLOCK_LIST_KEY,
- JSON.stringify(blockList),
+ await saveSetting(
+ c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY,
JSON.stringify(blockList)
- ).run();
+ );
return c.json({
success: true
})
diff --git a/worker/src/common.js b/worker/src/common.js
index d5977b96..946d2ddb 100644
--- a/worker/src/common.js
+++ b/worker/src/common.js
@@ -1,6 +1,6 @@
import { Jwt } from 'hono/utils/jwt'
-import { getDomains } from './utils';
+import { getDomains, getStringValue } from './utils';
export const newAddress = async (c, name, domain, enablePrefix) => {
// remove special characters
@@ -19,7 +19,7 @@ export const newAddress = async (c, name, domain, enablePrefix) => {
}
// create address
if (enablePrefix) {
- name = c.env.PREFIX + name + "@" + domain;
+ name = getStringValue(c.env.PREFIX) + name + "@" + domain;
} else {
name = name + "@" + domain;
}
@@ -53,3 +53,36 @@ export const newAddress = async (c, name, domain, enablePrefix) => {
jwt: jwt
})
}
+
+export const cleanup = async (c, cleanType, cleanDays) => {
+ if (!cleanType || !cleanDays || cleanDays < 0 || cleanDays > 30) {
+ throw new Error("Invalid cleanType or cleanDays")
+ }
+ console.log(`Cleanup ${cleanType} before ${cleanDays} days`);
+ switch (cleanType) {
+ case "mails":
+ await c.env.DB.prepare(`
+ DELETE FROM raw_mails WHERE created_at < datetime('now', '-${cleanDays} day')`
+ ).run();
+ break;
+ case "mails_unknow":
+ await c.env.DB.prepare(`
+ DELETE FROM raw_mails WHERE address NOT IN
+ (select name from address) AND created_at < datetime('now', '-${cleanDays} day')`
+ ).run();
+ break;
+ case "address":
+ await c.env.DB.prepare(`
+ DELETE FROM address WHERE updated_at < datetime('now', '-${cleanDays} day')`
+ ).run();
+ break;
+ case "sendbox":
+ await c.env.DB.prepare(`
+ DELETE FROM sendbox WHERE created_at < datetime('now', '-${cleanDays} day')`
+ ).run();
+ break;
+ default:
+ throw new Error("Invalid cleanType")
+ }
+ return true;
+}
diff --git a/worker/src/constants.js b/worker/src/constants.js
index 6f1630d6..2768998d 100644
--- a/worker/src/constants.js
+++ b/worker/src/constants.js
@@ -1,3 +1,4 @@
export const CONSTANTS = {
ADDRESS_BLOCK_LIST_KEY: 'address_block_list',
+ AUTO_CLEANUP_KEY: 'auto_cleanup',
}
diff --git a/worker/src/router.js b/worker/src/router.js
index b77eb756..b1fe679f 100644
--- a/worker/src/router.js
+++ b/worker/src/router.js
@@ -1,6 +1,8 @@
import { Hono } from 'hono'
-import { getDomains, getPasswords, getBooleanValue } from './utils';
+import {
+ getDomains, getPasswords, getBooleanValue, getJsonSetting
+} from './utils';
import { newAddress } from './common'
import { CONSTANTS } from './constants'
@@ -83,21 +85,6 @@ api.get('/api/settings', async (c) => {
} catch (e) {
console.warn("Failed to update address")
}
- let auto_reply = {};
- if (getBooleanValue(c.env.ENABLE_AUTO_REPLY)) {
- const results = await c.env.DB.prepare(
- `SELECT * FROM auto_reply_mails where address = ? `
- ).bind(address).first();
- if (results) {
- auto_reply = {
- subject: results.subject,
- message: results.message,
- enabled: results.enabled == 1,
- source_prefix: results.source_prefix,
- name: results.name,
- }
- }
- }
const { count: mailCountV1 } = await c.env.DB.prepare(
`SELECT count(*) as count FROM mails where address = ?`
).bind(address).first();
@@ -106,41 +93,12 @@ api.get('/api/settings', async (c) => {
where address = ? and enabled = 1`
).bind(address).first("balance");
return c.json({
- auto_reply: auto_reply,
address: address,
has_v1_mails: mailCountV1 && mailCountV1 > 0,
send_balance: balance || 0,
});
})
-api.post('/api/settings', async (c) => {
- const { address } = c.get("jwtPayload")
- if (!getBooleanValue(c.env.ENABLE_AUTO_REPLY)) {
- return c.text("Auto reply is disabled", 403)
- }
- const { auto_reply } = await c.req.json();
- const { name, subject, source_prefix, message, enabled } = auto_reply;
- if ((!subject || !message) && enabled) {
- return c.text("Invalid subject or message", 400)
- }
- else if (subject.length > 255 || message.length > 255) {
- return c.text("Subject or message too long", 400)
- }
- const { success } = await c.env.DB.prepare(
- `INSERT OR REPLACE INTO
- auto_reply_mails
- (name, address, source_prefix, subject, message, enabled)
- VALUES
- (?, ?, ?, ?, ?, ?)`
- ).bind(name || '', address, source_prefix || '', subject || '', message || '', enabled ? 1 : 0).run();
- if (!success) {
- return c.text("Failed to save settings", 500)
- }
- return c.json({
- success: success
- })
-})
-
api.get('/open_api/settings', async (c) => {
// check header x-custom-auth
let needAuth = false;
@@ -172,10 +130,8 @@ api.get('/api/new_address', async (c) => {
}
// check name block list
try {
- const value = await c.env.DB.prepare(
- `SELECT value FROM settings where key = ?`
- ).bind(CONSTANTS.ADDRESS_BLOCK_LIST_KEY).first("value");
- const blockList = value ? JSON.parse(value) : [];
+ const value = await getJsonSetting(c, CONSTANTS.ADDRESS_BLOCK_LIST_KEY);
+ const blockList = value || [];
if (blockList.some((item) => name.includes(item))) {
return c.text(`Name [${name}] is blocked`, 400)
}
diff --git a/worker/src/scheduled.js b/worker/src/scheduled.js
new file mode 100644
index 00000000..139f5a94
--- /dev/null
+++ b/worker/src/scheduled.js
@@ -0,0 +1,41 @@
+import { cleanup } from './common'
+import { CONSTANTS } from './constants'
+import { getJsonSetting } from './utils';
+
+export async function scheduled(event, env, ctx) {
+ console.log("Scheduled event: ", event);
+ let autoCleanupSetting = await getJsonSetting(
+ { env: env, },
+ CONSTANTS.AUTO_CLEANUP_KEY
+ );
+ console.log("autoCleanupSetting:", JSON.stringify(autoCleanupSetting));
+ autoCleanupSetting = autoCleanupSetting || {};
+ if (autoCleanupSetting.enableMailsAutoCleanup && autoCleanupSetting.cleanMailsDays > 0) {
+ await cleanup(
+ { env: env, },
+ "mails",
+ autoCleanupSetting.cleanMailsDays
+ );
+ }
+ if (autoCleanupSetting.enableUnknowMailsAutoCleanup && autoCleanupSetting.cleanUnknowMailsDays > 0) {
+ await cleanup(
+ { env: env, },
+ "mails_unknow",
+ autoCleanupSetting.cleanUnknowMailsDays
+ );
+ }
+ if (autoCleanupSetting.enableAddressAutoCleanup && autoCleanupSetting.cleanAddressDays > 0) {
+ await cleanup(
+ { env: env, },
+ "address",
+ autoCleanupSetting.cleanAddressDays
+ );
+ }
+ if (autoCleanupSetting.enableSendBoxAutoCleanup && autoCleanupSetting.cleanSendBoxDays > 0) {
+ await cleanup(
+ { env: env, },
+ "sendbox",
+ autoCleanupSetting.cleanSendBoxDays
+ );
+ }
+}
diff --git a/worker/src/user/auto_reply.js b/worker/src/user/auto_reply.js
new file mode 100644
index 00000000..bd671564
--- /dev/null
+++ b/worker/src/user/auto_reply.js
@@ -0,0 +1,52 @@
+import { getBooleanValue } from "../utils";
+
+
+export default {
+ getAutoReply: async (c) => {
+ if (!getBooleanValue(c.env.ENABLE_AUTO_REPLY)) {
+ return c.text("Auto reply is disabled", 403)
+ }
+ const { address } = c.get("jwtPayload")
+ const results = await c.env.DB.prepare(
+ `SELECT * FROM auto_reply_mails where address = ? `
+ ).bind(address).first();
+ if (!results) {
+ return c.json({});
+ }
+ return c.json({
+ subject: results.subject,
+ message: results.message,
+ enabled: results.enabled == 1,
+ source_prefix: results.source_prefix,
+ name: results.name,
+ })
+ },
+ saveAutoReply: async (c) => {
+ if (!getBooleanValue(c.env.ENABLE_AUTO_REPLY)) {
+ return c.text("Auto reply is disabled", 403)
+ }
+ const { address } = c.get("jwtPayload")
+ const { auto_reply } = await c.req.json();
+ const { name, subject, source_prefix, message, enabled } = auto_reply;
+ if ((!subject || !message) && enabled) {
+ return c.text("Invalid subject or message", 400)
+ }
+ else if (subject.length > 255 || message.length > 255) {
+ return c.text("Subject or message too long", 400)
+ }
+ const { success } = await c.env.DB.prepare(
+ `INSERT OR REPLACE INTO auto_reply_mails`
+ + ` (name, address, source_prefix, subject, message, enabled)`
+ + ` VALUES (?, ?, ?, ?, ?, ?)`
+ ).bind(
+ name || '', address, source_prefix || '',
+ subject || '', message || '', enabled ? 1 : 0
+ ).run();
+ if (!success) {
+ return c.text("Failed to auto_reply settings", 500)
+ }
+ return c.json({
+ success: success
+ })
+ }
+}
diff --git a/worker/src/user_api.js b/worker/src/user_api.js
new file mode 100644
index 00000000..6a2b881e
--- /dev/null
+++ b/worker/src/user_api.js
@@ -0,0 +1,10 @@
+import { Hono } from 'hono'
+
+import auto_reply from './user/auto_reply'
+
+const api = new Hono()
+
+api.get('/api/auto_reply', auto_reply.getAutoReply)
+api.post('/api/auto_reply', auto_reply.saveAutoReply)
+
+export { api }
diff --git a/worker/src/utils.js b/worker/src/utils.js
index a33612a4..72c646c0 100644
--- a/worker/src/utils.js
+++ b/worker/src/utils.js
@@ -1,5 +1,45 @@
import { createMimeMessage } from "mimetext";
+export const getJsonSetting = async (c, key) => {
+ const value = await getSetting(c, key);
+ if (!value) {
+ return null;
+ }
+ try {
+ return JSON.parse(value);
+ } catch (e) {
+ console.error(`GetJsonSetting: Failed to parse ${key}`, e);
+ }
+ return null;
+}
+
+export const getSetting = async (c, key) => {
+ try {
+ const value = await c.env.DB.prepare(
+ `SELECT value FROM settings where key = ?`
+ ).bind(key).first("value");
+ return value;
+ } catch (error) {
+ console.error(`GetSetting: Failed to get ${key}`, error);
+ }
+ return null;
+}
+
+export const saveSetting = async (c, key, value) => {
+ await c.env.DB.prepare(
+ `INSERT or REPLACE INTO settings (key, value) VALUES (?, ?)`
+ + ` ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')`
+ ).bind(key, value, value).run();
+ return true;
+}
+
+export const getStringValue = (value) => {
+ if (typeof value === "string") {
+ return value;
+ }
+ return "";
+}
+
export const getBooleanValue = (value) => {
if (typeof value === "boolean") {
return value;
diff --git a/worker/src/worker.js b/worker/src/worker.js
index 69c225b5..b8a420fc 100644
--- a/worker/src/worker.js
+++ b/worker/src/worker.js
@@ -3,10 +3,12 @@ import { cors } from 'hono/cors';
import { jwt } from 'hono/jwt'
import { api } from './router';
+import { api as userApi } from './user_api';
import { api as adminApi } from './admin_api';
import { api as apiV1 } from './api_v1';
import { api as apiSendMail } from './send_mail_api'
import { email } from './email';
+import { scheduled } from './scheduled';
import { getAdminPasswords, getPasswords } from './utils';
const app = new Hono()
@@ -53,6 +55,7 @@ app.use('/admin/*', async (c, next) => {
app.route('/', api)
+app.route('/', userApi)
app.route('/', adminApi)
app.route('/', apiV1)
app.route('/', apiSendMail)
@@ -65,4 +68,5 @@ app.all('/*', async c => c.text("Not Found", 404))
export default {
fetch: app.fetch,
email: email,
+ scheduled: scheduled,
}
diff --git a/worker/wrangler.toml.template b/worker/wrangler.toml.template
index 3b3a3799..6e2be371 100644
--- a/worker/wrangler.toml.template
+++ b/worker/wrangler.toml.template
@@ -7,6 +7,10 @@ node_compat = true
# { pattern = "temp-email-api.xxxxx.xyz", custom_domain = true },
# ]
+# enable cron if you want set auto clean up
+# [triggers]
+# crons = [ "0 0 * * *" ]
+
[vars]
PREFIX = "tmp"
# IF YOU WANT TO MAKE YOUR SITE PRIVATE, UNCOMMENT THE FOLLOWING LINES