mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-06 07:57:04 +08:00
feat: add mail id and url in webhook (#443)
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent, onMounted, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
import { useGlobalState } from '../store'
|
import { useGlobalState } from '../store'
|
||||||
import { api } from '../api'
|
import { api } from '../api'
|
||||||
@@ -17,6 +18,7 @@ import About from './common/About.vue';
|
|||||||
const SendMail = defineAsyncComponent(() => import('./index/SendMail.vue'));
|
const SendMail = defineAsyncComponent(() => import('./index/SendMail.vue'));
|
||||||
const { settings, openSettings, indexTab, globalTabplacement } = useGlobalState()
|
const { settings, openSettings, indexTab, globalTabplacement } = useGlobalState()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
const { t } = useI18n({
|
const { t } = useI18n({
|
||||||
messages: {
|
messages: {
|
||||||
@@ -30,6 +32,7 @@ const { t } = useI18n({
|
|||||||
s3Attachment: 'S3 Attachment',
|
s3Attachment: 'S3 Attachment',
|
||||||
saveToS3Success: 'save to s3 success',
|
saveToS3Success: 'save to s3 success',
|
||||||
webhookSettings: 'Webhook Settings',
|
webhookSettings: 'Webhook Settings',
|
||||||
|
query: 'Query',
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
mailbox: '收件箱',
|
mailbox: '收件箱',
|
||||||
@@ -41,11 +44,17 @@ const { t } = useI18n({
|
|||||||
s3Attachment: 'S3附件',
|
s3Attachment: 'S3附件',
|
||||||
saveToS3Success: '保存到s3成功',
|
saveToS3Success: '保存到s3成功',
|
||||||
webhookSettings: 'Webhook 设置',
|
webhookSettings: 'Webhook 设置',
|
||||||
|
query: '查询',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchMailData = async (limit, offset) => {
|
const fetchMailData = async (limit, offset) => {
|
||||||
|
if (mailIdQuery.value > 0) {
|
||||||
|
const singleMail = await api.fetch(`/api/mail/${mailIdQuery.value}`);
|
||||||
|
if (singleMail) return { results: [singleMail], count: 1 };
|
||||||
|
return { results: [], count: 0 };
|
||||||
|
}
|
||||||
return await api.fetch(`/api/mails?limit=${limit}&offset=${offset}`);
|
return await api.fetch(`/api/mails?limit=${limit}&offset=${offset}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,6 +89,30 @@ const saveToS3 = async (mail_id, filename, blob) => {
|
|||||||
message.error(error.message || "save to s3 error");
|
message.error(error.message || "save to s3 error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mailBoxKey = ref("")
|
||||||
|
const mailIdQuery = ref("")
|
||||||
|
const showMailIdQuery = ref(false)
|
||||||
|
|
||||||
|
const queryMail = () => {
|
||||||
|
mailBoxKey.value = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(route, () => {
|
||||||
|
if (!route.query.mail_id) {
|
||||||
|
showMailIdQuery.value = false;
|
||||||
|
mailIdQuery.value = "";
|
||||||
|
queryMail();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (route.query.mail_id) {
|
||||||
|
showMailIdQuery.value = true;
|
||||||
|
mailIdQuery.value = route.query.mail_id;
|
||||||
|
queryMail();
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -87,9 +120,17 @@ const saveToS3 = async (mail_id, filename, blob) => {
|
|||||||
<AddressBar />
|
<AddressBar />
|
||||||
<n-tabs v-if="settings.address" type="card" v-model:value="indexTab" :placement="globalTabplacement">
|
<n-tabs v-if="settings.address" type="card" v-model:value="indexTab" :placement="globalTabplacement">
|
||||||
<n-tab-pane name="mailbox" :tab="t('mailbox')">
|
<n-tab-pane name="mailbox" :tab="t('mailbox')">
|
||||||
<MailBox :showEMailTo="false" :showReply="true" :showSaveS3="openSettings.isS3Enabled" :saveToS3="saveToS3"
|
<div v-if="showMailIdQuery" style="margin-bottom: 10px;">
|
||||||
:enableUserDeleteEmail="openSettings.enableUserDeleteEmail" :fetchMailData="fetchMailData"
|
<n-input-group>
|
||||||
:deleteMail="deleteMail" />
|
<n-input v-model:value="mailIdQuery" />
|
||||||
|
<n-button @click="queryMail" type="primary" tertiary>
|
||||||
|
{{ t('query') }}
|
||||||
|
</n-button>
|
||||||
|
</n-input-group>
|
||||||
|
</div>
|
||||||
|
<MailBox :key="mailBoxKey" :showEMailTo="false" :showReply="true" :showSaveS3="openSettings.isS3Enabled"
|
||||||
|
:saveToS3="saveToS3" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
|
||||||
|
:fetchMailData="fetchMailData" :deleteMail="deleteMail" />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="sendbox" :tab="t('sendbox')">
|
<n-tab-pane name="sendbox" :tab="t('sendbox')">
|
||||||
<SendBox :fetchMailData="fetchSenboxData" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
|
<SendBox :fetchMailData="fetchSenboxData" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
|
||||||
|
|||||||
@@ -128,6 +128,8 @@ ENABLE_AUTO_REPLY = false
|
|||||||
# TG_BOT_INFO = "{}"
|
# TG_BOT_INFO = "{}"
|
||||||
# global forward address list, if set, all emails will be forwarded to these addresses
|
# global forward address list, if set, all emails will be forwarded to these addresses
|
||||||
# FORWARD_ADDRESS_LIST = ["xxx@xxx.com"]
|
# FORWARD_ADDRESS_LIST = ["xxx@xxx.com"]
|
||||||
|
# Frontend URL
|
||||||
|
# FRONTEND_URL = "https://xxxx.xxx"
|
||||||
|
|
||||||
[[d1_databases]]
|
[[d1_databases]]
|
||||||
binding = "DB"
|
binding = "DB"
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ ENABLE_AUTO_REPLY = false
|
|||||||
# TG_BOT_INFO = "{}"
|
# TG_BOT_INFO = "{}"
|
||||||
# 全局转发地址列表,如果不配置则不启用,启用后所有邮件都会转发到列表中的地址
|
# 全局转发地址列表,如果不配置则不启用,启用后所有邮件都会转发到列表中的地址
|
||||||
# FORWARD_ADDRESS_LIST = ["xxx@xxx.com"]
|
# FORWARD_ADDRESS_LIST = ["xxx@xxx.com"]
|
||||||
|
# 前端地址,用于发送 webhook 的邮件 url
|
||||||
|
# FRONTEND_URL = "https://xxxx.xxx"
|
||||||
|
|
||||||
# D1 数据库的名称和 ID 可以在 cloudflare 控制台查看
|
# D1 数据库的名称和 ID 可以在 cloudflare 控制台查看
|
||||||
[[d1_databases]]
|
[[d1_databases]]
|
||||||
|
|||||||
@@ -22,12 +22,14 @@ async function saveWebhookSettings(c: Context<HonoCustomType>): Promise<Response
|
|||||||
async function testWebhookSettings(c: Context<HonoCustomType>): Promise<Response> {
|
async function testWebhookSettings(c: Context<HonoCustomType>): Promise<Response> {
|
||||||
const settings = await c.req.json<WebhookSettings>();
|
const settings = await c.req.json<WebhookSettings>();
|
||||||
// random raw email
|
// random raw email
|
||||||
const raw = await c.env.DB.prepare(
|
const { id: mailId, raw } = await c.env.DB.prepare(
|
||||||
`SELECT raw FROM raw_mails ORDER BY RANDOM() LIMIT 1`
|
`SELECT id, raw FROM raw_mails ORDER BY RANDOM() LIMIT 1`
|
||||||
).first<string>("raw");
|
).first<{ id: string, raw: string }>() || {};
|
||||||
|
|
||||||
const parsedEmail = await commonParseMail(raw);
|
const parsedEmail = await commonParseMail(raw);
|
||||||
const res = await sendWebhook(settings, {
|
const res = await sendWebhook(settings, {
|
||||||
|
id: mailId || "0",
|
||||||
|
url: c.env.FRONTEND_URL ? `${c.env.FRONTEND_URL}?mail_id=${mailId}` : "",
|
||||||
from: parsedEmail?.sender || "test@test.com",
|
from: parsedEmail?.sender || "test@test.com",
|
||||||
to: "admin@test.com",
|
to: "admin@test.com",
|
||||||
subject: parsedEmail?.subject || "test subject",
|
subject: parsedEmail?.subject || "test subject",
|
||||||
|
|||||||
@@ -339,6 +339,7 @@ export async function sendWebhook(settings: WebhookSettings, formatMap: WebhookM
|
|||||||
);
|
);
|
||||||
/* eslint-enable no-useless-escape */
|
/* eslint-enable no-useless-escape */
|
||||||
}
|
}
|
||||||
|
console.log("send webhook", settings.url, settings.method, settings.headers, body);
|
||||||
const response = await fetch(settings.url, {
|
const response = await fetch(settings.url, {
|
||||||
method: settings.method,
|
method: settings.method,
|
||||||
headers: JSON.parse(settings.headers),
|
headers: JSON.parse(settings.headers),
|
||||||
@@ -354,7 +355,8 @@ export async function sendWebhook(settings: WebhookSettings, formatMap: WebhookM
|
|||||||
export async function triggerWebhook(
|
export async function triggerWebhook(
|
||||||
c: Context<HonoCustomType>,
|
c: Context<HonoCustomType>,
|
||||||
address: string,
|
address: string,
|
||||||
raw_mail: string
|
raw_mail: string,
|
||||||
|
message_id: string | null
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!c.env.KV || !getBooleanValue(c.env.ENABLE_WEBHOOK)) {
|
if (!c.env.KV || !getBooleanValue(c.env.ENABLE_WEBHOOK)) {
|
||||||
return
|
return
|
||||||
@@ -382,8 +384,14 @@ export async function triggerWebhook(
|
|||||||
if (webhookList.length === 0) {
|
if (webhookList.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const mailId = await c.env.DB.prepare(
|
||||||
|
`SELECT id FROM raw_mails where address = ? and message_id = ?`
|
||||||
|
).bind(address, message_id).first<string>("id");
|
||||||
|
|
||||||
const parsedEmail = await commonParseMail(raw_mail);
|
const parsedEmail = await commonParseMail(raw_mail);
|
||||||
const webhookMail = {
|
const webhookMail = {
|
||||||
|
id: mailId || "",
|
||||||
|
url: c.env.FRONTEND_URL ? `${c.env.FRONTEND_URL}?mail_id=${mailId}` : "",
|
||||||
from: parsedEmail?.sender || "",
|
from: parsedEmail?.sender || "",
|
||||||
to: address,
|
to: address,
|
||||||
subject: parsedEmail?.subject || "",
|
subject: parsedEmail?.subject || "",
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu
|
|||||||
try {
|
try {
|
||||||
await triggerWebhook(
|
await triggerWebhook(
|
||||||
{ env: env } as Context<HonoCustomType>,
|
{ env: env } as Context<HonoCustomType>,
|
||||||
message.to, rawEmail
|
message.to, rawEmail, message_id
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("send webhook error", error);
|
console.log("send webhook error", error);
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ api.get('/api/mails', async (c) => {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
api.get('/api/mail/:mail_id', async (c) => {
|
||||||
|
const { address } = c.get("jwtPayload")
|
||||||
|
const { mail_id } = c.req.param();
|
||||||
|
const result = await c.env.DB.prepare(
|
||||||
|
`SELECT * FROM raw_mails where id = ? and address = ?`
|
||||||
|
).bind(mail_id, address).first();
|
||||||
|
return c.json(result);
|
||||||
|
})
|
||||||
|
|
||||||
api.delete('/api/mails/:id', async (c) => {
|
api.delete('/api/mails/:id', async (c) => {
|
||||||
if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) {
|
if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) {
|
||||||
return c.text("User delete email is disabled", 403)
|
return c.text("User delete email is disabled", 403)
|
||||||
|
|||||||
@@ -36,12 +36,14 @@ async function testWebhookSettings(c: Context<HonoCustomType>): Promise<Response
|
|||||||
const settings = await c.req.json<WebhookSettings>();
|
const settings = await c.req.json<WebhookSettings>();
|
||||||
const { address } = c.get("jwtPayload");
|
const { address } = c.get("jwtPayload");
|
||||||
// random raw email
|
// random raw email
|
||||||
const raw = await c.env.DB.prepare(
|
const { id: mailId, raw } = await c.env.DB.prepare(
|
||||||
`SELECT raw FROM raw_mails WHERE address = ? ORDER BY RANDOM() LIMIT 1`
|
`SELECT id, raw FROM raw_mails WHERE address = ? ORDER BY RANDOM() LIMIT 1`
|
||||||
).bind(address).first<string>("raw");
|
).bind(address).first<{ id: string, raw: string }>() || {};
|
||||||
|
|
||||||
const parsedEmail = await commonParseMail(raw);
|
const parsedEmail = await commonParseMail(raw);
|
||||||
const res = await sendWebhook(settings, {
|
const res = await sendWebhook(settings, {
|
||||||
|
id: mailId || "0",
|
||||||
|
url: c.env.FRONTEND_URL ? `${c.env.FRONTEND_URL}?mail_id=${mailId}` : "",
|
||||||
from: parsedEmail?.sender || "test@test.com",
|
from: parsedEmail?.sender || "test@test.com",
|
||||||
to: address,
|
to: address,
|
||||||
subject: parsedEmail?.subject || "test subject",
|
subject: parsedEmail?.subject || "test subject",
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export class AdminWebhookSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type WebhookMail = {
|
export type WebhookMail = {
|
||||||
|
id: string;
|
||||||
|
url?: string;
|
||||||
from: string;
|
from: string;
|
||||||
to: string;
|
to: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
@@ -128,6 +130,8 @@ export class WebhookSettings {
|
|||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}, null, 2)
|
}, null, 2)
|
||||||
body: string = JSON.stringify({
|
body: string = JSON.stringify({
|
||||||
|
"id": "${id}",
|
||||||
|
"url": "${url}",
|
||||||
"from": "${from}",
|
"from": "${from}",
|
||||||
"to": "${to}",
|
"to": "${to}",
|
||||||
"subject": "${subject}",
|
"subject": "${subject}",
|
||||||
|
|||||||
Vendored
+3
@@ -61,6 +61,9 @@ export type Bindings = {
|
|||||||
TELEGRAM_BOT_TOKEN: string
|
TELEGRAM_BOT_TOKEN: string
|
||||||
TG_MAX_ADDRESS: number | undefined
|
TG_MAX_ADDRESS: number | undefined
|
||||||
TG_BOT_INFO: string | object | undefined
|
TG_BOT_INFO: string | object | undefined
|
||||||
|
|
||||||
|
// webhook config
|
||||||
|
FRONTEND_URL: string | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
type JwtPayload = {
|
type JwtPayload = {
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ ENABLE_AUTO_REPLY = false
|
|||||||
# TG_BOT_INFO = "{}"
|
# TG_BOT_INFO = "{}"
|
||||||
# global forward address list, if set, all emails will be forwarded to these addresses
|
# global forward address list, if set, all emails will be forwarded to these addresses
|
||||||
# FORWARD_ADDRESS_LIST = ["xxx@xxx.com"]
|
# FORWARD_ADDRESS_LIST = ["xxx@xxx.com"]
|
||||||
|
# Frontend URL
|
||||||
|
# FRONTEND_URL = "https://xxxx.xxx"
|
||||||
|
|
||||||
[[d1_databases]]
|
[[d1_databases]]
|
||||||
binding = "DB"
|
binding = "DB"
|
||||||
|
|||||||
Reference in New Issue
Block a user