优化站内发件无需第三方
This commit is contained in:
@@ -183,7 +183,6 @@ const en = {
|
|||||||
optional: 'Optional',
|
optional: 'Optional',
|
||||||
subjectInputDesc: 'Please enter the email subject',
|
subjectInputDesc: 'Please enter the email subject',
|
||||||
changeUserName: 'Change Username',
|
changeUserName: 'Change Username',
|
||||||
sendSeparately: 'Separately',
|
|
||||||
send: 'Send',
|
send: 'Send',
|
||||||
reply: 'Reply',
|
reply: 'Reply',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
|
|||||||
@@ -183,7 +183,6 @@ const zh = {
|
|||||||
optional: '可选',
|
optional: '可选',
|
||||||
subjectInputDesc: '请输入邮件主题',
|
subjectInputDesc: '请输入邮件主题',
|
||||||
changeUserName: '修改用户名',
|
changeUserName: '修改用户名',
|
||||||
sendSeparately: '分别发送',
|
|
||||||
send: '发送',
|
send: '发送',
|
||||||
reply: '回复',
|
reply: '回复',
|
||||||
confirm: '确定',
|
confirm: '确定',
|
||||||
|
|||||||
@@ -15,8 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<el-input-tag @add-tag="addTagChange" tag-type="primary" @input="inputChange" size="default" v-model="form.receiveEmail"
|
<el-input-tag @add-tag="addTagChange" tag-type="primary" @input="inputChange" size="default" v-model="form.receiveEmail" >
|
||||||
:placeholder="ruleEmailsInputDesc">
|
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<div class="item-title" >{{ $t('recipient') }}</div>
|
<div class="item-title" >{{ $t('recipient') }}</div>
|
||||||
<el-select
|
<el-select
|
||||||
@@ -37,18 +36,12 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<div style="display: flex;">
|
<div style="display: flex;margin-right: 3px;">
|
||||||
<Icon icon="fa7-solid:user-plus" width="20" height="20" class="add-contact" @click.stop="openContacts" />
|
<Icon icon="fa7-solid:user-plus" width="20" height="20" class="add-contact" @click.stop="openContacts" />
|
||||||
</div>
|
</div>
|
||||||
<span class="distribute" :class="form.manyType ? 'checked' : ''"
|
|
||||||
@click.stop="checkDistribute">{{ $t('sendSeparately') }}</span>
|
|
||||||
</template>
|
</template>
|
||||||
</el-input-tag>
|
</el-input-tag>
|
||||||
<el-input v-model="form.subject" :placeholder="$t('subjectInputDesc')">
|
<el-input v-model="form.subject" :placeholder="t('subject')" />
|
||||||
<template #prefix>
|
|
||||||
<div class="item-title">{{ $t('subject') }}</div>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
<tinyEditor :def-value="defValue" ref="editor" @change="change" @focus="focusChange" />
|
<tinyEditor :def-value="defValue" ref="editor" @change="change" @focus="focusChange" />
|
||||||
<div class="button-item">
|
<div class="button-item">
|
||||||
<div class="att-add" @click="chooseFile">
|
<div class="att-add" @click="chooseFile">
|
||||||
@@ -141,7 +134,6 @@ const contactsTabRef = ref({})
|
|||||||
const showContacts = ref(false)
|
const showContacts = ref(false)
|
||||||
const mySelect = ref()
|
const mySelect = ref()
|
||||||
let selectStatus = false
|
let selectStatus = false
|
||||||
const ruleEmailsInputDesc = ref(t('ruleEmailsInputDesc'))
|
|
||||||
const backReply = reactive({
|
const backReply = reactive({
|
||||||
receiveEmail: [],
|
receiveEmail: [],
|
||||||
subject: '',
|
subject: '',
|
||||||
@@ -152,7 +144,6 @@ const form = reactive({
|
|||||||
sendEmail: '',
|
sendEmail: '',
|
||||||
receiveEmail: [],
|
receiveEmail: [],
|
||||||
accountId: -1,
|
accountId: -1,
|
||||||
manyType: null,
|
|
||||||
name: '',
|
name: '',
|
||||||
subject: '',
|
subject: '',
|
||||||
content: '',
|
content: '',
|
||||||
@@ -255,10 +246,6 @@ function addTagChange(val) {
|
|||||||
if (selectStatus && has) openSelect()
|
if (selectStatus && has) openSelect()
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkDistribute() {
|
|
||||||
form.manyType = form.manyType ? null : 'divide'
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearContent() {
|
function clearContent() {
|
||||||
ElMessageBox.confirm(t('clearContentConfirm'), {
|
ElMessageBox.confirm(t('clearContentConfirm'), {
|
||||||
confirmButtonText: t('confirm'),
|
confirmButtonText: t('confirm'),
|
||||||
@@ -447,7 +434,11 @@ function openReply(email) {
|
|||||||
email.subject = email.subject || ''
|
email.subject = email.subject || ''
|
||||||
|
|
||||||
form.receiveEmail.push(email.sendEmail)
|
form.receiveEmail.push(email.sendEmail)
|
||||||
form.subject = (email.subject.startsWith('Re:') || email.subject.startsWith('回复:')) ? email.subject : 'Re: ' + email.subject
|
form.subject = (
|
||||||
|
email.subject.startsWith('Re:') ||
|
||||||
|
email.subject.startsWith('Re:') ||
|
||||||
|
email.subject.startsWith('回复:') ||
|
||||||
|
email.subject.startsWith('回复:')) ? email.subject : 'Re:' + email.subject
|
||||||
form.sendType = 'reply'
|
form.sendType = 'reply'
|
||||||
form.emailId = email.emailId
|
form.emailId = email.emailId
|
||||||
|
|
||||||
@@ -651,21 +642,6 @@ function close() {
|
|||||||
grid-template-rows: auto auto 1fr auto;
|
grid-template-rows: auto auto 1fr auto;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
|
|
||||||
.distribute {
|
|
||||||
color: var(--el-color-info);
|
|
||||||
background: var(--el-color-info-light-9);
|
|
||||||
border: var(--el-color-info-light-8);
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.distribute.checked {
|
|
||||||
background: var(--el-color-primary-light-9);
|
|
||||||
color: var(--el-color-primary) !important;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title {
|
.item-title {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,9 @@ import settingService from '../service/setting-service';
|
|||||||
import attService from '../service/att-service';
|
import attService from '../service/att-service';
|
||||||
import constant from '../const/constant';
|
import constant from '../const/constant';
|
||||||
import fileUtils from '../utils/file-utils';
|
import fileUtils from '../utils/file-utils';
|
||||||
import { emailConst, isDel, roleConst, settingConst } from '../const/entity-const';
|
import { emailConst, isDel, settingConst } from '../const/entity-const';
|
||||||
import emailUtils from '../utils/email-utils';
|
import emailUtils from '../utils/email-utils';
|
||||||
import roleService from '../service/role-service';
|
import roleService from '../service/role-service';
|
||||||
import verifyUtils from '../utils/verify-utils';
|
|
||||||
import userService from '../service/user-service';
|
import userService from '../service/user-service';
|
||||||
import telegramService from '../service/telegram-service';
|
import telegramService from '../service/telegram-service';
|
||||||
|
|
||||||
@@ -60,45 +59,16 @@ export async function email(message, env, ctx) {
|
|||||||
|
|
||||||
if (account && userRow.email !== env.admin) {
|
if (account && userRow.email !== env.admin) {
|
||||||
|
|
||||||
let { banEmail, banEmailType, availDomain } = await roleService.selectByUserId({ env: env }, account.userId);
|
let { banEmail, availDomain } = await roleService.selectByUserId({ env: env }, account.userId);
|
||||||
|
|
||||||
if (!roleService.hasAvailDomainPerm(availDomain, message.to)) {
|
if (!roleService.hasAvailDomainPerm(availDomain, message.to)) {
|
||||||
message.setReject('Mailbox disabled');
|
message.setReject('The recipient is not authorized to use this domain.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
banEmail = banEmail.split(',').filter(item => item !== '');
|
if(roleService.isBanEmail(banEmail, email.from.address)) {
|
||||||
|
message.setReject('The recipient is disabled from receiving emails.');
|
||||||
|
return;
|
||||||
if (banEmail.includes('*')) {
|
|
||||||
|
|
||||||
if (!banEmailHandler(banEmailType, message, email)) return;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const item of banEmail) {
|
|
||||||
|
|
||||||
if (verifyUtils.isDomain(item)) {
|
|
||||||
|
|
||||||
const banDomain = item.toLowerCase();
|
|
||||||
const receiveDomain = emailUtils.getDomain(email.from.address.toLowerCase());
|
|
||||||
|
|
||||||
if (banDomain === receiveDomain) {
|
|
||||||
|
|
||||||
if (!banEmailHandler(banEmailType, message, email)) return;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (item.toLowerCase() === email.from.address.toLowerCase()) {
|
|
||||||
|
|
||||||
if (!banEmailHandler(banEmailType, message, email)) return;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -199,20 +169,3 @@ export async function email(message, env, ctx) {
|
|||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function banEmailHandler(banEmailType, message, email) {
|
|
||||||
|
|
||||||
if (banEmailType === roleConst.banEmailType.ALL) {
|
|
||||||
message.setReject('Mailbox disabled');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (banEmailType === roleConst.banEmailType.CONTENT) {
|
|
||||||
email.html = 'The content has been deleted';
|
|
||||||
email.text = 'The content has been deleted';
|
|
||||||
email.attachments = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
|
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
|
||||||
import { sql } from 'drizzle-orm';
|
import { sql } from 'drizzle-orm';
|
||||||
|
|
||||||
export const att = sqliteTable('attachments', {
|
export const att = sqliteTable('attachments', {
|
||||||
attId: integer('att_id').primaryKey({ autoIncrement: true }),
|
attId: integer('att_id').primaryKey({ autoIncrement: true }),
|
||||||
userId: integer('user_id').notNull(),
|
userId: integer('user_id').notNull(),
|
||||||
emailId: integer('email_id').notNull(),
|
emailId: integer('email_id').notNull(),
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const en = {
|
|||||||
noOsDomainSendAtt: 'Cannot send attachments: object storage domain not configured',
|
noOsDomainSendAtt: 'Cannot send attachments: object storage domain not configured',
|
||||||
noOsSendAtt: 'Cannot send attachments: object storage not configured',
|
noOsSendAtt: 'Cannot send attachments: object storage not configured',
|
||||||
disabledSend: 'Email sending feature is disabled',
|
disabledSend: 'Email sending feature is disabled',
|
||||||
noSeparateSend: 'Attachments are not supported in separate sending',
|
|
||||||
daySendLimit: 'Daily send limit reached',
|
daySendLimit: 'Daily send limit reached',
|
||||||
totalSendLimit: 'Total send limit reached',
|
totalSendLimit: 'Total send limit reached',
|
||||||
daySendLack: 'Not enough remaining sends today',
|
daySendLack: 'Not enough remaining sends today',
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const zh = {
|
|||||||
noOsDomainSendAtt: '对象存储域名未配置不能发送附件',
|
noOsDomainSendAtt: '对象存储域名未配置不能发送附件',
|
||||||
noOsSendAtt: '对象存储未配置不能发送附件',
|
noOsSendAtt: '对象存储未配置不能发送附件',
|
||||||
disabledSend: '邮件发送功能已停用',
|
disabledSend: '邮件发送功能已停用',
|
||||||
noSeparateSend: '分别发送暂时不支持附件',
|
|
||||||
daySendLimit: '发送次数已到达每日限制',
|
daySendLimit: '发送次数已到达每日限制',
|
||||||
totalSendLimit: '发送次数已到达限制',
|
totalSendLimit: '发送次数已到达限制',
|
||||||
daySendLack: '当日剩余发送次数不足',
|
daySendLack: '当日剩余发送次数不足',
|
||||||
|
|||||||
@@ -171,6 +171,9 @@ const attService = {
|
|||||||
attData.emailId = emailId;
|
attData.emailId = emailId;
|
||||||
attData.accountId = accountId;
|
attData.accountId = accountId;
|
||||||
attData.type = attConst.type.EMBED;
|
attData.type = attConst.type.EMBED;
|
||||||
|
if (!attData.buff) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
await r2Service.putObj(c, attData.key, attData.buff, {
|
await r2Service.putObj(c, attData.key, attData.buff, {
|
||||||
contentType: attData.mimeType,
|
contentType: attData.mimeType,
|
||||||
cacheControl: `max-age=259200`,
|
cacheControl: `max-age=259200`,
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import kvConst from '../const/kv-const';
|
|||||||
import { t } from '../i18n/i18n'
|
import { t } from '../i18n/i18n'
|
||||||
import domainUtils from '../utils/domain-uitls';
|
import domainUtils from '../utils/domain-uitls';
|
||||||
import account from "../entity/account";
|
import account from "../entity/account";
|
||||||
import {sleep} from "../utils/time-utils";
|
import { att } from '../entity/att';
|
||||||
|
import telegramService from './telegram-service';
|
||||||
|
|
||||||
const emailService = {
|
const emailService = {
|
||||||
|
|
||||||
@@ -147,25 +148,26 @@ const emailService = {
|
|||||||
return orm(c).insert(email).values({ ...params }).returning().get();
|
return orm(c).insert(email).values({ ...params }).returning().get();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//邮件发送
|
||||||
async send(c, params, userId) {
|
async send(c, params, userId) {
|
||||||
|
|
||||||
let {
|
let {
|
||||||
accountId,
|
accountId, //发送账号id
|
||||||
name,
|
name, //发件人名字
|
||||||
sendType,
|
sendType, //发件类型
|
||||||
emailId,
|
emailId, //邮件id,如果是回复邮件会带
|
||||||
receiveEmail,
|
receiveEmail, //收件人邮箱
|
||||||
manyType,
|
text, //邮件纯文本
|
||||||
text,
|
content, //邮件内容
|
||||||
content,
|
subject, //邮件标题
|
||||||
subject,
|
attachments //附件
|
||||||
attachments
|
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
const { resendTokens, r2Domain, send } = await settingService.query(c);
|
const { resendTokens, r2Domain, send, domainList } = await settingService.query(c);
|
||||||
|
|
||||||
let { imageDataList, html } = await attService.toImageUrlHtml(c, content);
|
let { imageDataList, html } = await attService.toImageUrlHtml(c, content);
|
||||||
|
|
||||||
|
//判断是否关闭发件功能
|
||||||
if (send === settingConst.send.CLOSE) {
|
if (send === settingConst.send.CLOSE) {
|
||||||
throw new BizError(t('disabledSend'), 403);
|
throw new BizError(t('disabledSend'), 403);
|
||||||
}
|
}
|
||||||
@@ -173,10 +175,12 @@ const emailService = {
|
|||||||
const userRow = await userService.selectById(c, userId);
|
const userRow = await userService.selectById(c, userId);
|
||||||
const roleRow = await roleService.selectById(c, userRow.type);
|
const roleRow = await roleService.selectById(c, userRow.type);
|
||||||
|
|
||||||
|
//如果不是管理员,发送被禁用
|
||||||
if (c.env.admin !== userRow.email && roleRow.sendType === 'ban') {
|
if (c.env.admin !== userRow.email && roleRow.sendType === 'ban') {
|
||||||
throw new BizError(t('bannedSend'), 403);
|
throw new BizError(t('bannedSend'), 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//如果不是管理员,权限设置了发送次数
|
||||||
if (c.env.admin !== userRow.email && roleRow.sendCount) {
|
if (c.env.admin !== userRow.email && roleRow.sendCount) {
|
||||||
|
|
||||||
if (userRow.sendCount >= roleRow.sendCount) {
|
if (userRow.sendCount >= roleRow.sendCount) {
|
||||||
@@ -191,11 +195,6 @@ const emailService = {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attachments.length > 0 && manyType === 'divide') {
|
|
||||||
throw new BizError(t('noSeparateSend'));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const accountRow = await accountService.selectById(c, accountId);
|
const accountRow = await accountService.selectById(c, accountId);
|
||||||
|
|
||||||
if (!accountRow) {
|
if (!accountRow) {
|
||||||
@@ -207,21 +206,28 @@ const emailService = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (c.env.admin !== userRow.email) {
|
if (c.env.admin !== userRow.email) {
|
||||||
|
//用户没有这个域名的使用权限
|
||||||
if(!roleService.hasAvailDomainPerm(roleRow.availDomain, accountRow.email)) {
|
if(!roleService.hasAvailDomainPerm(roleRow.availDomain, accountRow.email)) {
|
||||||
throw new BizError(t('noDomainPermSend'),403)
|
throw new BizError(t('noDomainPermSend'),403)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//判断接收方是不是全部为站内邮箱
|
||||||
|
const allInternal = receiveEmail.every(email => {
|
||||||
|
const domain = '@' + emailUtils.getDomain(email);
|
||||||
|
return domainList.includes(domain);
|
||||||
|
});
|
||||||
|
|
||||||
const domain = emailUtils.getDomain(accountRow.email);
|
const domain = emailUtils.getDomain(accountRow.email);
|
||||||
const resendToken = resendTokens[domain];
|
const resendToken = resendTokens[domain];
|
||||||
|
|
||||||
if (!resendToken) {
|
//如果接收方存在站外邮箱,又没有resend token
|
||||||
|
if (!resendToken && !allInternal) {
|
||||||
throw new BizError(t('noResendToken'));
|
throw new BizError(t('noResendToken'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//没有发件人名字自动截取
|
||||||
if (!name) {
|
if (!name) {
|
||||||
name = emailUtils.getName(accountRow.email);
|
name = emailUtils.getName(accountRow.email);
|
||||||
}
|
}
|
||||||
@@ -230,6 +236,7 @@ const emailService = {
|
|||||||
messageId: null
|
messageId: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//如果是回复邮件
|
||||||
if (sendType === 'reply') {
|
if (sendType === 'reply') {
|
||||||
|
|
||||||
emailRow = await this.selectById(c, emailId);
|
emailRow = await this.selectById(c, emailId);
|
||||||
@@ -240,37 +247,12 @@ const emailService = {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let resendResult = null;
|
let resendResult = {};
|
||||||
|
|
||||||
const resend = new Resend(resendToken);
|
//存在站外时邮箱全部由resend发送
|
||||||
|
if (!allInternal) {
|
||||||
|
|
||||||
//如果是分开发送
|
const resend = new Resend(resendToken);
|
||||||
if (manyType === 'divide') {
|
|
||||||
|
|
||||||
let sendFormList = [];
|
|
||||||
|
|
||||||
receiveEmail.forEach(email => {
|
|
||||||
const sendForm = {
|
|
||||||
from: `${name} <${accountRow.email}>`,
|
|
||||||
to: [email],
|
|
||||||
subject: subject,
|
|
||||||
text: text,
|
|
||||||
html: html
|
|
||||||
};
|
|
||||||
|
|
||||||
if (sendType === 'reply') {
|
|
||||||
sendForm.headers = {
|
|
||||||
'in-reply-to': emailRow.messageId,
|
|
||||||
'references': emailRow.messageId
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
sendFormList.push(sendForm);
|
|
||||||
});
|
|
||||||
|
|
||||||
resendResult = await resend.batch.send(sendFormList);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
const sendForm = {
|
const sendForm = {
|
||||||
from: `${name} <${accountRow.email}>`,
|
from: `${name} <${accountRow.email}>`,
|
||||||
@@ -304,6 +286,7 @@ const emailService = {
|
|||||||
//把图片标签cid标签切换会通用url
|
//把图片标签cid标签切换会通用url
|
||||||
html = this.imgReplace(html, imageDataList, r2Domain);
|
html = this.imgReplace(html, imageDataList, r2Domain);
|
||||||
|
|
||||||
|
//封装数据保存到数据库
|
||||||
const emailData = {};
|
const emailData = {};
|
||||||
emailData.sendEmail = accountRow.email;
|
emailData.sendEmail = accountRow.email;
|
||||||
emailData.name = name;
|
emailData.name = name;
|
||||||
@@ -311,72 +294,54 @@ const emailService = {
|
|||||||
emailData.content = html;
|
emailData.content = html;
|
||||||
emailData.text = text;
|
emailData.text = text;
|
||||||
emailData.accountId = accountId;
|
emailData.accountId = accountId;
|
||||||
|
emailData.status = emailConst.status.SENT;
|
||||||
emailData.type = emailConst.type.SEND;
|
emailData.type = emailConst.type.SEND;
|
||||||
emailData.userId = userId;
|
emailData.userId = userId;
|
||||||
emailData.status = emailConst.status.SENT;
|
emailData.resendEmailId = data?.id;
|
||||||
|
|
||||||
const emailDataList = [];
|
const recipient = [];
|
||||||
|
|
||||||
if (manyType === 'divide') {
|
receiveEmail.forEach(item => {
|
||||||
|
recipient.push({ address: item, name: '' });
|
||||||
|
});
|
||||||
|
|
||||||
receiveEmail.forEach((item, index) => {
|
emailData.recipient = JSON.stringify(recipient);
|
||||||
const emailDataItem = { ...emailData };
|
|
||||||
emailDataItem.resendEmailId = data.data[index].id;
|
|
||||||
emailDataItem.recipient = JSON.stringify([{ address: item, name: '' }]);
|
|
||||||
emailDataList.push(emailDataItem);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
emailData.resendEmailId = data.id;
|
|
||||||
|
|
||||||
const recipient = [];
|
|
||||||
|
|
||||||
receiveEmail.forEach(item => {
|
|
||||||
recipient.push({ address: item, name: '' });
|
|
||||||
});
|
|
||||||
|
|
||||||
emailData.recipient = JSON.stringify(recipient);
|
|
||||||
|
|
||||||
emailDataList.push(emailData);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendType === 'reply') {
|
if (sendType === 'reply') {
|
||||||
emailDataList.forEach(emailData => {
|
emailData.inReplyTo = emailRow.messageId;
|
||||||
emailData.inReplyTo = emailRow.messageId;
|
emailData.relation = emailRow.messageId;
|
||||||
emailData.relation = emailRow.messageId;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//如果权限有发送次数增加用户发送次数
|
||||||
if (roleRow.sendCount) {
|
if (roleRow.sendCount) {
|
||||||
await userService.incrUserSendCount(c, receiveEmail.length, userId);
|
await userService.incrUserSendCount(c, receiveEmail.length, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailRowList = await Promise.all(
|
//保存到数据库并返回结果
|
||||||
|
const emailResult = await orm(c).insert(email).values(emailData).returning().get();
|
||||||
|
|
||||||
emailDataList.map(async (emailData) => {
|
//保存内嵌附件
|
||||||
const emailRow = await orm(c).insert(email).values(emailData).returning().get();
|
if (imageDataList.length > 0) {
|
||||||
|
await attService.saveArticleAtt(c, imageDataList, userId, accountId, emailResult.emailId);
|
||||||
|
}
|
||||||
|
|
||||||
if (imageDataList.length > 0) {
|
//保存普通附件
|
||||||
await attService.saveArticleAtt(c, imageDataList, userId, accountId, emailRow.emailId);
|
if (attachments?.length > 0) {
|
||||||
}
|
await attService.saveSendAtt(c, attachments, userId, accountId, emailResult.emailId);
|
||||||
|
}
|
||||||
|
|
||||||
if (attachments?.length > 0) {
|
const attList = await attService.selectByEmailIds(c, [emailResult.emailId]);
|
||||||
await attService.saveSendAtt(c, attachments, userId, accountId, emailRow.emailId);
|
emailResult.attList = attList;
|
||||||
}
|
|
||||||
|
|
||||||
const attsList = await attService.selectByEmailIds(c, [emailRow.emailId]);
|
//如果全是站内接收方,直接写入数据库
|
||||||
emailRow.attList = attsList;
|
if (allInternal) {
|
||||||
|
await this.HandleOnSiteEmail(c, receiveEmail, emailResult, attList);
|
||||||
return emailRow;
|
}
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const dateStr = dayjs().format('YYYY-MM-DD');
|
const dateStr = dayjs().format('YYYY-MM-DD');
|
||||||
|
|
||||||
let daySendTotal = await c.env.kv.get(kvConst.SEND_DAY_COUNT + dateStr);
|
let daySendTotal = await c.env.kv.get(kvConst.SEND_DAY_COUNT + dateStr);
|
||||||
|
|
||||||
|
//记录每天发件次数统计
|
||||||
if (!daySendTotal) {
|
if (!daySendTotal) {
|
||||||
await c.env.kv.put(kvConst.SEND_DAY_COUNT + dateStr, JSON.stringify(receiveEmail.length), { expirationTtl: 60 * 60 * 24 });
|
await c.env.kv.put(kvConst.SEND_DAY_COUNT + dateStr, JSON.stringify(receiveEmail.length), { expirationTtl: 60 * 60 * 24 });
|
||||||
} else {
|
} else {
|
||||||
@@ -384,7 +349,117 @@ const emailService = {
|
|||||||
await c.env.kv.put(kvConst.SEND_DAY_COUNT + dateStr, JSON.stringify(daySendTotal), { expirationTtl: 60 * 60 * 24 });
|
await c.env.kv.put(kvConst.SEND_DAY_COUNT + dateStr, JSON.stringify(daySendTotal), { expirationTtl: 60 * 60 * 24 });
|
||||||
}
|
}
|
||||||
|
|
||||||
return emailRowList;
|
return [ emailResult ];
|
||||||
|
},
|
||||||
|
|
||||||
|
//处理站内邮件发送
|
||||||
|
async HandleOnSiteEmail(c, receiveEmail, sendEmailData, attList) {
|
||||||
|
|
||||||
|
const { noRecipient } = await settingService.query(c);
|
||||||
|
|
||||||
|
//查询所有收件人账号信息
|
||||||
|
let accountList = await orm(c).select().from(account).where(inArray(account.email, receiveEmail)).all();
|
||||||
|
|
||||||
|
//查询所有收件人权限身份
|
||||||
|
const userIds = accountList.map(accountRow => accountRow.userId);
|
||||||
|
let roleList = await roleService.selectByUserIds(c, userIds);
|
||||||
|
|
||||||
|
//封装数据库准备保存到数据库
|
||||||
|
const emailDataList = [];
|
||||||
|
|
||||||
|
for (const email of receiveEmail) {
|
||||||
|
|
||||||
|
//把发件人邮件改成收件
|
||||||
|
const emailValues = {...sendEmailData}
|
||||||
|
emailValues.status = emailConst.status.RECEIVE;
|
||||||
|
emailValues.type = emailConst.type.RECEIVE;
|
||||||
|
emailValues.toEmail = email;
|
||||||
|
emailValues.toName = emailUtils.getName(email);
|
||||||
|
emailValues.emailId = null;
|
||||||
|
|
||||||
|
const accountRow = accountList.find(accountRow => accountRow.email === email);
|
||||||
|
|
||||||
|
//如果收件人存在就把邮件信息改成收件人的
|
||||||
|
if (accountRow) {
|
||||||
|
|
||||||
|
//设置给收件人保存
|
||||||
|
emailValues.userId = accountRow.userId;
|
||||||
|
emailValues.accountId = accountRow.accountId;
|
||||||
|
emailValues.type = emailConst.type.RECEIVE;
|
||||||
|
emailValues.status = emailConst.status.RECEIVE;
|
||||||
|
|
||||||
|
const roleRow = roleList.find(roleRow => roleRow.userId === accountRow.userId);
|
||||||
|
|
||||||
|
let { banEmail, availDomain } = roleRow;
|
||||||
|
|
||||||
|
//如果收件人没有这个域名的使用权限和有邮件拦截,就把邮件改为拒收状态
|
||||||
|
if (email !== c.env.admin) {
|
||||||
|
|
||||||
|
if (!roleService.hasAvailDomainPerm(availDomain, email)) {
|
||||||
|
emailValues.status = emailConst.status.BOUNCED;
|
||||||
|
emailValues.message = `The recipient <${email}> is not authorized to use this domain.`;
|
||||||
|
} else if(roleService.isBanEmail(banEmail, sendEmailData.sendEmail)) {
|
||||||
|
emailValues.status = emailConst.status.BOUNCED;
|
||||||
|
emailValues.message = `The recipient <${email}> is disabled from receiving emails.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
emailDataList.push(emailValues);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//设置无收件人邮件信息
|
||||||
|
emailValues.userId = 0;
|
||||||
|
emailValues.accountId = 0;
|
||||||
|
emailValues.type = emailConst.type.RECEIVE;
|
||||||
|
emailValues.status = emailConst.status.NOONE;
|
||||||
|
|
||||||
|
//如果无人收件关闭改为拒收
|
||||||
|
if (noRecipient === settingConst.noRecipient.CLOSE) {
|
||||||
|
emailValues.status = emailConst.status.BOUNCED;
|
||||||
|
emailValues.message = `Recipient not found: <${email}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
emailDataList.push(emailValues);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//保存邮件
|
||||||
|
const receiveEmailList = emailDataList.filter(emailRow => emailRow.status === emailConst.status.RECEIVE || emailRow.status === emailConst.status.NOONE);
|
||||||
|
|
||||||
|
for (const emailData of receiveEmailList) {
|
||||||
|
|
||||||
|
const emailRow = await orm(c).insert(email).values(emailData).returning().get();
|
||||||
|
|
||||||
|
//设置附件保存
|
||||||
|
for (const attRow of attList) {
|
||||||
|
const attValues = {...attRow};
|
||||||
|
attValues.emailId = emailRow.emailId;
|
||||||
|
attValues.accountId = emailRow.accountId;
|
||||||
|
attValues.userId = emailRow.userId;
|
||||||
|
attValues.attId = null;
|
||||||
|
await orm(c).insert(att).values(attValues).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const bouncedEmail = emailDataList.find(emailRow => emailRow.status === emailConst.status.BOUNCED);
|
||||||
|
|
||||||
|
|
||||||
|
let status = emailConst.status.DELIVERED;
|
||||||
|
let message = ''
|
||||||
|
//如果有拒收邮件,就把发件人的邮件改成拒收
|
||||||
|
if (bouncedEmail) {
|
||||||
|
const messageJson = { message: bouncedEmail.message };
|
||||||
|
message = JSON.stringify(messageJson);
|
||||||
|
status = emailConst.status.BOUNCED;
|
||||||
|
}
|
||||||
|
|
||||||
|
await orm(c).update(email).set({ status: emailConst.status.DELIVERED, message: message }).where(eq(email.emailId, sendEmailData.emailId)).run();
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
imgReplace(content, cidAttList, r2domain) {
|
imgReplace(content, cidAttList, r2domain) {
|
||||||
|
|||||||
@@ -6,17 +6,18 @@ const resendService = {
|
|||||||
|
|
||||||
async webhooks(c, body) {
|
async webhooks(c, body) {
|
||||||
|
|
||||||
const params = {}
|
const params = {
|
||||||
console.error(body)
|
resendEmailId: body.data.email_id,
|
||||||
|
status: emailConst.status.SENT
|
||||||
|
}
|
||||||
|
|
||||||
if (body.type === 'email.delivered') {
|
if (body.type === 'email.delivered') {
|
||||||
params.status = emailConst.status.DELIVERED
|
params.status = emailConst.status.DELIVERED
|
||||||
params.resendEmailId = body.data.email_id
|
|
||||||
params.message = null
|
params.message = null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body.type === 'email.complained') {
|
if (body.type === 'email.complained') {
|
||||||
params.status = emailConst.status.COMPLAINED
|
params.status = emailConst.status.COMPLAINED
|
||||||
params.resendEmailId = body.data.email_id
|
|
||||||
params.message = null
|
params.message = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,19 +25,16 @@ const resendService = {
|
|||||||
let bounce = body.data.bounce
|
let bounce = body.data.bounce
|
||||||
bounce = JSON.stringify(bounce);
|
bounce = JSON.stringify(bounce);
|
||||||
params.status = emailConst.status.BOUNCED
|
params.status = emailConst.status.BOUNCED
|
||||||
params.resendEmailId = body.data.email_id
|
|
||||||
params.message = bounce
|
params.message = bounce
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body.type === 'email.delivery_delayed') {
|
if (body.type === 'email.delivery_delayed') {
|
||||||
params.status = emailConst.status.DELAYED
|
params.status = emailConst.status.DELAYED
|
||||||
params.resendEmailId = body.data.email_id
|
|
||||||
params.message = null
|
params.message = null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body.type === 'email.failed') {
|
if (body.type === 'email.failed') {
|
||||||
params.status = emailConst.status.FAILED
|
params.status = emailConst.status.FAILED
|
||||||
params.resendEmailId = body.data.email_id
|
|
||||||
params.message = body.data.failed.reason
|
params.message = body.data.failed.reason
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -174,6 +174,50 @@ const roleService = {
|
|||||||
|
|
||||||
selectByName(c, roleName) {
|
selectByName(c, roleName) {
|
||||||
return orm(c).select().from(role).where(eq(role.name, roleName)).get();
|
return orm(c).select().from(role).where(eq(role.name, roleName)).get();
|
||||||
|
},
|
||||||
|
|
||||||
|
selectByUserIds(c, userIds) {
|
||||||
|
|
||||||
|
if (!userIds && userIds.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return orm(c).select({ ...role, userId: user.userId }).from(user).leftJoin(role, eq(role.roleId, user.type)).where(inArray(user.userId, userIds)).all();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
isBanEmail(banEmail, fromEmail) {
|
||||||
|
|
||||||
|
banEmail = banEmail.split(',').filter(item => item !== '');
|
||||||
|
|
||||||
|
if (banEmail.includes('*')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of banEmail) {
|
||||||
|
|
||||||
|
if (verifyUtils.isDomain(item)) {
|
||||||
|
|
||||||
|
const banDomain = item.toLowerCase();
|
||||||
|
const receiveDomain = emailUtils.getDomain(fromEmail.toLowerCase());
|
||||||
|
|
||||||
|
if (banDomain === receiveDomain) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (item.toLowerCase() === fromEmail.toLowerCase()) {
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const fileUtils = {
|
|||||||
async getBuffHash(buff) {
|
async getBuffHash(buff) {
|
||||||
const hashBuffer = await crypto.subtle.digest('SHA-256', buff);
|
const hashBuffer = await crypto.subtle.digest('SHA-256', buff);
|
||||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
return hashArray.slice(0, 16).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||||
},
|
},
|
||||||
|
|
||||||
base64ToDataStr(base64) {
|
base64ToDataStr(base64) {
|
||||||
|
|||||||
Reference in New Issue
Block a user