mirror of
https://github.com/beilunyang/moemail.git
synced 2026-09-05 23:36:37 +08:00
feat: Add Webhook integration and update README with new features
This commit is contained in:
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## 特性
|
## 特性
|
||||||
|
|
||||||
- 🔒 **隐私保护**:保护您的真实邮箱地址,远离垃圾邮件和不必要的订阅
|
- 🔒 **隐私保护**:保护您的真实邮箱地址,远离垃圾邮件和不必要的订阅
|
||||||
@@ -38,6 +40,7 @@
|
|||||||
- 📱 **PWA 支持**:支持 PWA 安装
|
- 📱 **PWA 支持**:支持 PWA 安装
|
||||||
- 💸 **免费自部署**:基于 Cloudflare 构建, 可实现免费自部署,无需任何费用
|
- 💸 **免费自部署**:基于 Cloudflare 构建, 可实现免费自部署,无需任何费用
|
||||||
- 🎉 **可爱的 UI**:简洁可爱萌萌哒 UI 界面
|
- 🎉 **可爱的 UI**:简洁可爱萌萌哒 UI 界面
|
||||||
|
- 🔔 **Webhook 通知**:支持通过 webhook 接收新邮件通知
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
@@ -194,6 +197,57 @@ git push origin v1.0.0
|
|||||||
- 在 Settings 中选择变量和机密
|
- 在 Settings 中选择变量和机密
|
||||||
- 添加 AUTH_GITHUB_ID, AUTH_GITHUB_SECRET, AUTH_SECRET
|
- 添加 AUTH_GITHUB_ID, AUTH_GITHUB_SECRET, AUTH_SECRET
|
||||||
|
|
||||||
|
## Webhook 集成
|
||||||
|
|
||||||
|
当收到新邮件时,系统会向用户配置并且已启用的 Webhook URL 发送 POST 请求。
|
||||||
|
|
||||||
|
### 请求头
|
||||||
|
```http
|
||||||
|
Content-Type: application/json
|
||||||
|
X-Webhook-Event: new_message
|
||||||
|
```
|
||||||
|
|
||||||
|
### 请求体
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"emailId": "email-uuid",
|
||||||
|
"messageId": "message-uuid",
|
||||||
|
"fromAddress": "sender@example.com",
|
||||||
|
"subject": "邮件主题",
|
||||||
|
"content": "邮件文本内容",
|
||||||
|
"html": "邮件HTML内容",
|
||||||
|
"receivedAt": "2024-01-01T12:00:00.000Z",
|
||||||
|
"toAddress": "your-email@moemail.app"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置说明
|
||||||
|
1. 点击个人头像,进入个人中心
|
||||||
|
2. 在个人中心启用 Webhook
|
||||||
|
3. 设置接收通知的 URL
|
||||||
|
4. 点击测试按钮验证配置
|
||||||
|
5. 保存配置后即可接收新邮件通知
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
|
||||||
|
项目提供了一个简单的测试服务器, 可以通过如下命令运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm webhook-test-server
|
||||||
|
```
|
||||||
|
|
||||||
|
测试服务器会在本地启动一个 HTTP 服务器,监听 3001 端口(http://localhost:3001),并打印收到的 Webhook 消息详情。
|
||||||
|
|
||||||
|
如果需要进行外网测试,可以通过 Cloudflare Tunnel 将服务暴露到外网:
|
||||||
|
```bash
|
||||||
|
pnpx cloudflared tunnel --url http://localhost:3001
|
||||||
|
```
|
||||||
|
|
||||||
|
### 注意事项
|
||||||
|
- Webhook 接口应在 10 秒内响应
|
||||||
|
- 非 2xx 响应码会触发重试
|
||||||
|
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
|
|
||||||
欢迎提交 Pull Request 或者 Issue来帮助改进这个项目
|
欢迎提交 Pull Request 或者 Issue来帮助改进这个项目
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Input } from "@/components/ui/input"
|
|||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch"
|
||||||
import { useToast } from "@/components/ui/use-toast"
|
import { useToast } from "@/components/ui/use-toast"
|
||||||
import { Loader2, Send } from "lucide-react"
|
import { Loader2, Send, ChevronDown, ChevronUp } from "lucide-react"
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -20,6 +20,8 @@ export function WebhookConfig() {
|
|||||||
const [url, setUrl] = useState("")
|
const [url, setUrl] = useState("")
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [testing, setTesting] = useState(false)
|
const [testing, setTesting] = useState(false)
|
||||||
|
const [showDocs, setShowDocs] = useState(false)
|
||||||
|
const [initialLoading, setInitialLoading] = useState(true)
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -30,8 +32,17 @@ export function WebhookConfig() {
|
|||||||
setUrl(data.url)
|
setUrl(data.url)
|
||||||
})
|
})
|
||||||
.catch(console.error)
|
.catch(console.error)
|
||||||
|
.finally(() => setInitialLoading(false))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
if (initialLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center py-8">
|
||||||
|
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!url) return
|
if (!url) return
|
||||||
@@ -105,49 +116,86 @@ export function WebhookConfig() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{enabled && (
|
{enabled && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-4">
|
||||||
<Label htmlFor="webhook-url">Webhook URL</Label>
|
<div className="space-y-2">
|
||||||
<div className="flex gap-2">
|
<Label htmlFor="webhook-url">Webhook URL</Label>
|
||||||
<Input
|
<div className="flex gap-2">
|
||||||
id="webhook-url"
|
<Input
|
||||||
placeholder="https://example.com/webhook"
|
id="webhook-url"
|
||||||
value={url}
|
placeholder="https://example.com/webhook"
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
value={url}
|
||||||
type="url"
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
required
|
type="url"
|
||||||
/>
|
required
|
||||||
<Button type="submit" disabled={loading} className="w-20">
|
/>
|
||||||
{loading ? (
|
<Button type="submit" disabled={loading} className="w-20">
|
||||||
<Loader2 className="w-4 h-4 animate-spin" />
|
{loading ? (
|
||||||
) : (
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
"保存"
|
) : (
|
||||||
)}
|
"保存"
|
||||||
</Button>
|
)}
|
||||||
<TooltipProvider>
|
</Button>
|
||||||
<Tooltip>
|
<TooltipProvider>
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<Button
|
<TooltipTrigger asChild>
|
||||||
type="button"
|
<Button
|
||||||
variant="outline"
|
type="button"
|
||||||
onClick={handleTest}
|
variant="outline"
|
||||||
disabled={testing || !url}
|
onClick={handleTest}
|
||||||
>
|
disabled={testing || !url}
|
||||||
{testing ? (
|
>
|
||||||
<Loader2 className="w-4 h-4 animate-spin" />
|
{testing ? (
|
||||||
) : (
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
<Send className="w-4 h-4" />
|
) : (
|
||||||
)}
|
<Send className="w-4 h-4" />
|
||||||
</Button>
|
)}
|
||||||
</TooltipTrigger>
|
</Button>
|
||||||
<TooltipContent>
|
</TooltipTrigger>
|
||||||
<p>发送测试消息到此 Webhook</p>
|
<TooltipContent>
|
||||||
</TooltipContent>
|
<p>发送测试消息到此 Webhook</p>
|
||||||
</Tooltip>
|
</TooltipContent>
|
||||||
</TooltipProvider>
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
我们会向此 URL 发送 POST 请求,包含新邮件的相关信息
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
onClick={() => setShowDocs(!showDocs)}
|
||||||
|
>
|
||||||
|
{showDocs ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
||||||
|
查看数据格式说明
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showDocs && (
|
||||||
|
<div className="rounded-md bg-muted p-4 text-sm space-y-3">
|
||||||
|
<p>当收到新邮件时,我们会向配置的 URL 发送 POST 请求,请求头包含:</p>
|
||||||
|
<pre className="bg-background p-2 rounded text-xs">
|
||||||
|
Content-Type: application/json{'\n'}
|
||||||
|
X-Webhook-Event: new_message
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<p>请求体示例:</p>
|
||||||
|
<pre className="bg-background p-2 rounded text-xs overflow-auto">
|
||||||
|
{`{
|
||||||
|
"emailId": "email-uuid",
|
||||||
|
"messageId": "message-uuid",
|
||||||
|
"fromAddress": "sender@example.com",
|
||||||
|
"subject": "邮件主题",
|
||||||
|
"content": "邮件文本内容",
|
||||||
|
"html": "邮件HTML内容",
|
||||||
|
"receivedAt": "2024-01-01T12:00:00.000Z",
|
||||||
|
"toAddress": "your-email@${window.location.host}"
|
||||||
|
}`}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
我们会向此 URL 发送 POST 请求,包含新邮件的相关信息
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user