feat: add Cloudflare Email Sending support
This commit is contained in:
@@ -21,6 +21,7 @@ const en = {
|
||||
totalSendLack: 'Not enough total remaining sends',
|
||||
senderAccountNotExist: 'Sender email does not exist',
|
||||
noResendToken: 'Resend API token not configured',
|
||||
noSendProvider: 'Email sending service is not configured',
|
||||
sendEmailNotCurUser: 'Sender email does not belong to current user',
|
||||
notExistEmailReply: 'Mail does not exist and cannot be replied to',
|
||||
imageAttLimit: 'The maximum number of image attachments is 10',
|
||||
|
||||
@@ -21,6 +21,7 @@ const zh = {
|
||||
totalSendLack: '剩余发送次数不足',
|
||||
senderAccountNotExist: '发件人邮箱不存在',
|
||||
noResendToken: 'Resend未配置,只能给站内邮箱发件',
|
||||
noSendProvider: '发信服务未配置,只能给站内邮箱发件',
|
||||
sendEmailNotCurUser: '发件人邮箱非当前用户所有',
|
||||
notExistEmailReply: '邮件不存在无法回复',
|
||||
imageAttLimit: '图片不能超过10个',
|
||||
|
||||
@@ -160,7 +160,7 @@ const emailService = {
|
||||
text, //邮件纯文本
|
||||
content, //邮件内容
|
||||
subject, //邮件标题
|
||||
attachments //附件
|
||||
attachments = [] //附件
|
||||
} = params;
|
||||
|
||||
const { resendTokens, r2Domain, send, domainList } = await settingService.query(c);
|
||||
@@ -230,10 +230,11 @@ const emailService = {
|
||||
|
||||
const domain = emailUtils.getDomain(accountRow.email);
|
||||
const resendToken = resendTokens[domain];
|
||||
const useCloudflareEmail = !!c.env.EMAIL;
|
||||
|
||||
//如果接收方存在站外邮箱,又没有resend token
|
||||
if (!resendToken && !allInternal) {
|
||||
throw new BizError(t('noResendToken'));
|
||||
//如果接收方存在站外邮箱,又没有发信服务
|
||||
if (!useCloudflareEmail && !resendToken && !allInternal) {
|
||||
throw new BizError(t('noSendProvider'));
|
||||
}
|
||||
|
||||
//没有发件人名字自动截取
|
||||
@@ -256,34 +257,40 @@ const emailService = {
|
||||
|
||||
}
|
||||
|
||||
let resendResult = {};
|
||||
let sendResult = {};
|
||||
|
||||
//存在站外时邮箱全部由resend发送
|
||||
//存在站外邮箱时,如果配置了 Cloudflare Email Service 就优先使用,否则使用 Resend
|
||||
if (!allInternal) {
|
||||
|
||||
const resend = new Resend(resendToken);
|
||||
|
||||
const sendForm = {
|
||||
from: `${name} <${accountRow.email}>`,
|
||||
to: [...receiveEmail],
|
||||
subject: subject,
|
||||
text: text,
|
||||
html: html,
|
||||
attachments: [...imageDataList, ...attachments]
|
||||
};
|
||||
|
||||
if (sendType === 'reply') {
|
||||
sendForm.headers = {
|
||||
'in-reply-to': emailRow.messageId,
|
||||
'references': emailRow.messageId
|
||||
};
|
||||
if (useCloudflareEmail) {
|
||||
sendResult = await this.sendByCloudflareEmail(c, {
|
||||
name,
|
||||
accountEmail: accountRow.email,
|
||||
receiveEmail,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
attachments: [...imageDataList, ...attachments],
|
||||
sendType,
|
||||
messageId: emailRow.messageId
|
||||
});
|
||||
} else {
|
||||
sendResult = await this.sendByResend(resendToken, {
|
||||
name,
|
||||
accountEmail: accountRow.email,
|
||||
receiveEmail,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
attachments: [...imageDataList, ...attachments],
|
||||
sendType,
|
||||
messageId: emailRow.messageId
|
||||
});
|
||||
}
|
||||
|
||||
resendResult = await resend.emails.send(sendForm);
|
||||
|
||||
}
|
||||
|
||||
const { data, error } = resendResult;
|
||||
const { data, error } = sendResult;
|
||||
|
||||
|
||||
if (error) {
|
||||
@@ -367,6 +374,104 @@ const emailService = {
|
||||
return [ emailResult ];
|
||||
},
|
||||
|
||||
async sendByCloudflareEmail(c, params) {
|
||||
const sendForm = {
|
||||
from: { email: params.accountEmail, name: params.name },
|
||||
to: [...params.receiveEmail],
|
||||
subject: params.subject
|
||||
};
|
||||
|
||||
if (params.text) {
|
||||
sendForm.text = params.text;
|
||||
}
|
||||
|
||||
if (params.html) {
|
||||
sendForm.html = params.html;
|
||||
}
|
||||
|
||||
const attachments = await this.toCloudflareAttachments(params.attachments);
|
||||
if (attachments.length > 0) {
|
||||
sendForm.attachments = attachments;
|
||||
}
|
||||
|
||||
if (params.sendType === 'reply' && params.messageId) {
|
||||
sendForm.headers = {
|
||||
'in-reply-to': params.messageId,
|
||||
'references': params.messageId
|
||||
};
|
||||
}
|
||||
|
||||
const result = await c.env.EMAIL.send(sendForm);
|
||||
|
||||
return {
|
||||
data: {
|
||||
id: result.messageId
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
async sendByResend(resendToken, params) {
|
||||
const resend = new Resend(resendToken);
|
||||
|
||||
const sendForm = {
|
||||
from: `${params.name} <${params.accountEmail}>`,
|
||||
to: [...params.receiveEmail],
|
||||
subject: params.subject,
|
||||
text: params.text,
|
||||
html: params.html,
|
||||
attachments: params.attachments
|
||||
};
|
||||
|
||||
if (params.sendType === 'reply') {
|
||||
sendForm.headers = {
|
||||
'in-reply-to': params.messageId,
|
||||
'references': params.messageId
|
||||
};
|
||||
}
|
||||
|
||||
return await resend.emails.send(sendForm);
|
||||
},
|
||||
|
||||
async toCloudflareAttachments(attachments) {
|
||||
|
||||
const result = [];
|
||||
|
||||
for (const attachment of attachments) {
|
||||
let content = attachment.content;
|
||||
|
||||
if (!content && attachment.path) {
|
||||
const response = await fetch(attachment.path);
|
||||
if (!response.ok) {
|
||||
throw new BizError(`Attachment fetch failed: ${attachment.filename}`);
|
||||
}
|
||||
content = await response.arrayBuffer();
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof content === 'string' && content.startsWith('data:')) {
|
||||
content = content.split(',')[1] || content;
|
||||
}
|
||||
|
||||
const item = {
|
||||
content,
|
||||
filename: attachment.filename,
|
||||
type: attachment.mimeType || attachment.contentType || attachment.type || 'application/octet-stream',
|
||||
disposition: attachment.contentId ? 'inline' : 'attachment'
|
||||
};
|
||||
|
||||
if (attachment.contentId) {
|
||||
item.contentId = attachment.contentId.replace(/^<|>$/g, '');
|
||||
}
|
||||
|
||||
result.push(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//处理站内邮件发送
|
||||
async HandleOnSiteEmail(c, receiveEmail, sendEmailData, attList) {
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ enabled = true
|
||||
#binding = "r2" #r2对象存储绑定名默认不可修改
|
||||
#bucket_name = "" #r2对象存储桶的名字
|
||||
|
||||
#[[send_email]]
|
||||
#name = "EMAIL" #配置后站外发信优先使用 Cloudflare Email Service
|
||||
|
||||
[ai]
|
||||
binding = "AI"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user