|
|
@@ -25,6 +25,7 @@ import {
|
|
|
Select,
|
|
|
Skeleton,
|
|
|
Space,
|
|
|
+ Switch,
|
|
|
Table,
|
|
|
Tabs,
|
|
|
Typography
|
|
|
@@ -69,7 +70,7 @@ export default function DomainDetail() {
|
|
|
const activeSection: DetailSection = isSection(section) ? section : 'overview';
|
|
|
const navigate = useNavigate();
|
|
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
|
- const { config } = useAppContext();
|
|
|
+ const { config, user } = useAppContext();
|
|
|
const { locale } = useI18n();
|
|
|
const copy = locale.startsWith('en') ? enCopy : zhCopy;
|
|
|
const { message } = App.useApp();
|
|
|
@@ -255,7 +256,8 @@ export default function DomainDetail() {
|
|
|
spfExtra: target.spfExtra,
|
|
|
dmarcPolicy: target.dmarcPolicy,
|
|
|
dmarcRua: target.dmarcRua,
|
|
|
- catchAllAddress: target.catchAllAddress
|
|
|
+ catchAllAddress: target.catchAllAddress,
|
|
|
+ mailboxSignupEnabled: target.mailboxSignupEnabled
|
|
|
});
|
|
|
setEditOpen(true);
|
|
|
}
|
|
|
@@ -305,6 +307,8 @@ export default function DomainDetail() {
|
|
|
|
|
|
if (!domain || !base || !health) return null;
|
|
|
|
|
|
+ const canManageMailboxSignup = user?.role === 'admin' || user?.id === domain.userId;
|
|
|
+
|
|
|
const menuItems: MenuProps['items'] = [
|
|
|
{ key: 'check', icon: <ReloadOutlined />, label: copy.checkDns, onClick: () => void checkDns() },
|
|
|
{ key: 'apply', icon: <ThunderboltOutlined />, label: copy.autoDns, disabled: !domain.dnsCredentialId, onClick: () => void applyDns() },
|
|
|
@@ -356,12 +360,12 @@ export default function DomainDetail() {
|
|
|
{ key: 'overview', label: copy.overview, children: <OverviewTab domain={domain} health={health} dnsName={base.dnsCredentials.find((item) => item.id === domain.dnsCredentialId)?.name} relayName={base.smtpRelays.find((item) => item.id === domain.smtpRelayId)?.name} copy={copy} onCheck={checkDns} onReviewDns={() => navigate(`/domains/${domain.id}/dns`)} onEdit={() => openEdit(domain)} onDelete={() => setDeleteOpen(true)} loading={actionLoading} /> },
|
|
|
{ key: 'dns', label: copy.dnsAndVerification, children: <DnsTab domain={domain} copy={copy} onCopy={copyValue} onCheck={checkDns} onApply={applyDns} loading={actionLoading} /> },
|
|
|
{ key: 'sending', label: copy.sendingConfiguration, children: <SectionContent loading={sectionLoading} error={sectionError} retry={loadSection} copy={copy}><SendingTab domain={domain} config={config} credential={smtpCredential} tokens={apiTokens || []} relayName={base.smtpRelays.find((item) => item.id === domain.smtpRelayId)?.name} copy={copy} onCopy={copyValue} onEdit={() => openEdit(domain)} /></SectionContent> },
|
|
|
- { key: 'inbound', label: copy.inboundConfiguration, children: <SectionContent loading={sectionLoading} error={sectionError} retry={loadSection} copy={copy}><InboundTab domain={domain} config={config} mailboxes={mailboxes || []} copy={copy} onNavigate={() => navigate('/inbox')} onEdit={() => openEdit(domain)} /></SectionContent> },
|
|
|
+ { key: 'inbound', label: copy.inboundConfiguration, children: <SectionContent loading={sectionLoading} error={sectionError} retry={loadSection} copy={copy}><InboundTab domain={domain} config={config} mailboxes={mailboxes || []} copy={copy} canManageMailboxSignup={canManageMailboxSignup} onNavigate={() => navigate('/inbox')} onEdit={() => openEdit(domain)} /></SectionContent> },
|
|
|
{ key: 'activity', label: copy.activity, children: <SectionContent loading={sectionLoading} error={sectionError} retry={loadSection} copy={copy}><ActivityTab events={events || []} copy={copy} onView={(event) => navigate(`/activity/${event.id}?domainId=${domain.id}`)} /></SectionContent> }
|
|
|
]}
|
|
|
/>
|
|
|
|
|
|
- <EditDomainModal open={editOpen} domain={domain} form={editForm} dnsCredentials={base.dnsCredentials} smtpRelays={base.smtpRelays} copy={copy} loading={Boolean(actionLoading.edit)} onCancel={closeEdit} onSave={saveDomain} />
|
|
|
+ <EditDomainModal open={editOpen} domain={domain} form={editForm} dnsCredentials={base.dnsCredentials} smtpRelays={base.smtpRelays} copy={copy} canManageMailboxSignup={canManageMailboxSignup} loading={Boolean(actionLoading.edit)} onCancel={closeEdit} onSave={saveDomain} />
|
|
|
|
|
|
<Modal title={copy.deleteTitle} open={deleteOpen} okText={copy.delete} cancelText={copy.cancel} okButtonProps={{ danger: true, disabled: deleteConfirmation !== domain.domain, loading: Boolean(actionLoading.delete) }} onCancel={() => { setDeleteOpen(false); setDeleteConfirmation(''); }} onOk={() => void deleteDomain()}>
|
|
|
<Space direction="vertical" size={16} className="full-width"><Alert type="error" showIcon message={copy.deleteWarning} /><Typography.Text>{copy.typeDomain} <Typography.Text code>{domain.domain}</Typography.Text></Typography.Text><Input value={deleteConfirmation} onChange={(event) => setDeleteConfirmation(event.target.value)} aria-label={copy.deleteConfirmation} style={{ minHeight: 44 }} /></Space>
|
|
|
@@ -441,12 +445,12 @@ function SendingTab({ domain, config, credential, tokens, relayName, copy, onCop
|
|
|
);
|
|
|
}
|
|
|
|
|
|
-function InboundTab({ domain, config, mailboxes, copy, onNavigate, onEdit }: { domain: Domain; config: ReturnType<typeof useAppContext>['config']; mailboxes: InboundMailbox[]; copy: typeof zhCopy; onNavigate: () => void; onEdit: () => void }) {
|
|
|
+function InboundTab({ domain, config, mailboxes, copy, canManageMailboxSignup, onNavigate, onEdit }: { domain: Domain; config: ReturnType<typeof useAppContext>['config']; mailboxes: InboundMailbox[]; copy: typeof zhCopy; canManageMailboxSignup: boolean; onNavigate: () => void; onEdit: () => void }) {
|
|
|
return (
|
|
|
<Row gutter={[16, 16]}>
|
|
|
<Col xs={24} xl={9}><SectionCard title={copy.inboundStatus}><Space direction="vertical" size={16} className="full-width"><StatusPill tone={config?.submission?.inboundEnabled ? 'success' : 'warning'}><InboxOutlined /> {config?.submission?.inboundEnabled ? copy.inboundEnabled : copy.inboundDisabled}</StatusPill><Typography.Text type="secondary">{copy.inboundHint}</Typography.Text><Button onClick={onNavigate}>{copy.openMailboxRouting}</Button></Space></SectionCard></Col>
|
|
|
<Col xs={24} xl={15}><SectionCard title={copy.mailboxes} extra={<Typography.Text type="secondary">{mailboxes.length}</Typography.Text>}>{mailboxes.length ? <List dataSource={mailboxes} renderItem={(mailbox) => <List.Item><List.Item.Meta title={mailbox.address} description={`${mailbox.unreadCount} ${copy.unread} · ${mailbox.messageCount} ${copy.messages}`} /><StatusPill tone={mailbox.status === 'active' ? 'success' : 'warning'}>{mailbox.status}</StatusPill></List.Item>} /> : <EmptyState description={copy.noMailboxes} action={<Button onClick={onNavigate}>{copy.configureMailbox}</Button>} />}</SectionCard></Col>
|
|
|
- <Col span={24}><SectionCard title={copy.catchAll} extra={<Button icon={<EditOutlined />} onClick={onEdit}>{copy.edit}</Button>}><Descriptions column={1}><Descriptions.Item label={copy.catchAllAddress}>{domain.catchAllAddress || copy.notConfigured}</Descriptions.Item><Descriptions.Item label="IMAP">{config?.mailAccess?.imap.enabled ? config.mailAccess.imap.ports.map((item) => item.port).join(', ') : copy.disabled}</Descriptions.Item><Descriptions.Item label="POP3">{config?.mailAccess?.pop3.enabled ? config.mailAccess.pop3.ports.map((item) => item.port).join(', ') : copy.disabled}</Descriptions.Item></Descriptions></SectionCard></Col>
|
|
|
+ <Col span={24}><SectionCard title={copy.catchAll} extra={<Button icon={<EditOutlined />} onClick={onEdit}>{copy.edit}</Button>}><Descriptions column={1}><Descriptions.Item label={copy.catchAllAddress}>{domain.catchAllAddress || copy.notConfigured}</Descriptions.Item><Descriptions.Item label={copy.mailboxSignupPolicy}><Space direction="vertical" size={4}><StatusPill tone={domain.mailboxSignupEnabled ? 'success' : 'neutral'}>{domain.mailboxSignupEnabled ? copy.mailboxSignupOpen : copy.mailboxSignupOwnerOnly}</StatusPill>{canManageMailboxSignup ? <Typography.Text type="secondary">{copy.mailboxSignupHint}</Typography.Text> : null}</Space></Descriptions.Item><Descriptions.Item label="IMAP">{config?.mailAccess?.imap.enabled ? config.mailAccess.imap.ports.map((item) => item.port).join(', ') : copy.disabled}</Descriptions.Item><Descriptions.Item label="POP3">{config?.mailAccess?.pop3.enabled ? config.mailAccess.pop3.ports.map((item) => item.port).join(', ') : copy.disabled}</Descriptions.Item></Descriptions></SectionCard></Col>
|
|
|
</Row>
|
|
|
);
|
|
|
}
|
|
|
@@ -467,10 +471,10 @@ function SectionContent({ loading, error, retry, copy, children }: { loading: bo
|
|
|
return <>{children}</>;
|
|
|
}
|
|
|
|
|
|
-function EditDomainModal({ open, domain, form, dnsCredentials, smtpRelays, copy, loading, onCancel, onSave }: { open: boolean; domain: Domain; form: ReturnType<typeof Form.useForm<DomainPatchPayload>>[0]; dnsCredentials: DnsCredential[]; smtpRelays: SmtpRelay[]; copy: typeof zhCopy; loading: boolean; onCancel: () => void; onSave: () => void }) {
|
|
|
+function EditDomainModal({ open, domain, form, dnsCredentials, smtpRelays, copy, canManageMailboxSignup, loading, onCancel, onSave }: { open: boolean; domain: Domain; form: ReturnType<typeof Form.useForm<DomainPatchPayload>>[0]; dnsCredentials: DnsCredential[]; smtpRelays: SmtpRelay[]; copy: typeof zhCopy; canManageMailboxSignup: boolean; loading: boolean; onCancel: () => void; onSave: () => void }) {
|
|
|
return (
|
|
|
<Modal title={copy.editTitle} open={open} width={640} confirmLoading={loading} okText={copy.save} cancelText={copy.cancel} onCancel={onCancel} onOk={onSave}>
|
|
|
- <Form form={form} layout="vertical"><Row gutter={16}><Col xs={24} md={12}><Form.Item name="dnsCredentialId" label={copy.dnsIntegration}><Select allowClear options={dnsCredentials.map((item) => ({ value: item.id, label: item.name }))} /></Form.Item></Col><Col xs={24} md={12}><Form.Item name="smtpRelayId" label={copy.relay}><Select allowClear options={smtpRelays.map((item) => ({ value: item.id, label: item.name }))} /></Form.Item></Col></Row><Row gutter={16}><Col xs={24} md={12}><Form.Item name="senderHost" label={copy.senderHost} rules={[{ required: true }]}><Input /></Form.Item></Col><Col xs={24} md={12}><Form.Item name="sendingIp" label={copy.sendingIp} rules={[{ required: true }]}><Input /></Form.Item></Col></Row><Row gutter={16}><Col xs={24} md={12}><Form.Item name="selector" label="DKIM selector" rules={[{ required: true }]}><Input /></Form.Item></Col><Col xs={24} md={12}><Form.Item name="dmarcPolicy" label="DMARC"><Select options={['none', 'quarantine', 'reject'].map((value) => ({ value, label: value }))} /></Form.Item></Col></Row><Form.Item name="spfExtra" label="SPF"><Input.TextArea rows={2} /></Form.Item><Form.Item name="dmarcRua" label="DMARC rua"><Input /></Form.Item><Form.Item name="catchAllAddress" label={copy.catchAllAddress}><Input placeholder={`inbox@${domain.domain}`} /></Form.Item></Form>
|
|
|
+ <Form form={form} layout="vertical"><Row gutter={16}><Col xs={24} md={12}><Form.Item name="dnsCredentialId" label={copy.dnsIntegration}><Select allowClear options={dnsCredentials.map((item) => ({ value: item.id, label: item.name }))} /></Form.Item></Col><Col xs={24} md={12}><Form.Item name="smtpRelayId" label={copy.relay}><Select allowClear options={smtpRelays.map((item) => ({ value: item.id, label: item.name }))} /></Form.Item></Col></Row><Row gutter={16}><Col xs={24} md={12}><Form.Item name="senderHost" label={copy.senderHost} rules={[{ required: true }]}><Input /></Form.Item></Col><Col xs={24} md={12}><Form.Item name="sendingIp" label={copy.sendingIp} rules={[{ required: true }]}><Input /></Form.Item></Col></Row><Row gutter={16}><Col xs={24} md={12}><Form.Item name="selector" label="DKIM selector" rules={[{ required: true }]}><Input /></Form.Item></Col><Col xs={24} md={12}><Form.Item name="dmarcPolicy" label="DMARC"><Select options={['none', 'quarantine', 'reject'].map((value) => ({ value, label: value }))} /></Form.Item></Col></Row><Form.Item name="spfExtra" label="SPF"><Input.TextArea rows={2} /></Form.Item><Form.Item name="dmarcRua" label="DMARC rua"><Input /></Form.Item><Form.Item name="catchAllAddress" label={copy.catchAllAddress}><Input placeholder={`inbox@${domain.domain}`} /></Form.Item>{canManageMailboxSignup ? <Form.Item name="mailboxSignupEnabled" label={copy.mailboxSignupPolicy} valuePropName="checked" extra={copy.mailboxSignupExtra}><Switch checkedChildren={copy.enabled} unCheckedChildren={copy.disabled} /></Form.Item> : null}</Form>
|
|
|
</Modal>
|
|
|
);
|
|
|
}
|
|
|
@@ -505,9 +509,9 @@ const zhCopy = {
|
|
|
dnsPassed: 'DNS 已通过', needsAttention: '需要处理', testSend: '测试发送', checkDns: '检查 DNS', autoDns: '自动写入 DNS', edit: '编辑配置', delete: '删除域名', checked: 'DNS 检查已完成', dnsApplied: 'DNS 记录写入完成', dnsPartial: '部分 DNS 记录写入失败', saved: '配置已保存', testQueued: '测试邮件已加入队列', deleted: '域名已删除', actionFailed: '操作失败', copied: '已复制', copyFailed: '复制失败', setupPartialTitle: '域名已创建,但 DNS 后续操作需要处理', setupPartialDescription: '自动写入或即时检查未完全成功。请查看下方逐条结果,修正后重新执行。', setupCompleteTitle: '域名创建完成', setupAutomaticDescription: 'DNS 自动写入流程已执行,请核对逐条结果和当前验证状态。', setupManualDescription: '请按下方记录完成手动 DNS 配置,并在记录生效后重新检查。',
|
|
|
domainSummary: '域名摘要', authenticationHealth: '认证健康度', recordsPassed: '项必需记录已通过', ready: '发送就绪', nextActions: '下一步操作', reviewDns: '查看 DNS 记录', dangerZone: '危险操作', dangerHint: '删除域名会停止相关发信配置,且不可撤销。', recheck: '重新检查', copyAll: '复制全部', domainVerified: '域名已验证,可用于发送', domainNotVerified: '域名尚未完成验证', lastChecked: '最近检查', never: '从未检查', noDnsRecords: '尚未生成 DNS 检查结果', checkNow: '立即检查', warnings: '需要注意', applyResults: 'DNS 写入结果', applyComplete: '全部完成', applyPartial: '部分失败', applySucceeded: '成功', applyFailed: '失败', applySkipped: '已跳过', applySucceededHint: '记录已写入或无需变更。', applyFailedHint: '未返回具体错误,请重新执行或改为手动配置。',
|
|
|
host: '主机', ports: '端口', username: '用户名', password: '密码', configured: '已设置(不会返回明文)', notConfigured: '未设置', manageKeys: '管理 API 密钥', availableKeys: '可用密钥', noKeys: '尚无可用 API 密钥', senderIdentity: '发件身份', fromDomain: '发件域名', verification: '验证状态', verified: '已验证', pending: '等待验证',
|
|
|
- inboundStatus: '收信服务状态', inboundEnabled: '收信服务已启用', inboundDisabled: '收信服务未启用', inboundHint: '邮箱、别名和路由在独立收件箱工作区管理。', openMailboxRouting: '打开邮箱与路由', mailboxes: '邮箱', unread: '封未读', messages: '封邮件', noMailboxes: '该域名还没有邮箱', configureMailbox: '配置邮箱', catchAll: '默认收信路由', catchAllAddress: 'Catch-all 地址', disabled: '未启用',
|
|
|
+ inboundStatus: '收信服务状态', inboundEnabled: '收信服务已启用', inboundDisabled: '收信服务未启用', inboundHint: '邮箱、别名和路由在独立收件箱工作区管理。', openMailboxRouting: '打开邮箱与路由', mailboxes: '邮箱', unread: '封未读', messages: '封邮件', noMailboxes: '该域名还没有邮箱', configureMailbox: '配置邮箱', catchAll: '默认收信路由', catchAllAddress: 'Catch-all 地址', mailboxSignupPolicy: '邮箱创建权限', mailboxSignupOpen: '已共享给其他用户', mailboxSignupOwnerOnly: '仅域名拥有者', mailboxSignupHint: '开启后,其他用户可以在该域名下创建自己的邮箱,但不能管理此域名配置。', mailboxSignupExtra: '共享只开放邮箱创建能力,不开放 DNS、Catch-all、删除等域名管理权限。', enabled: '开放', disabled: '未启用',
|
|
|
time: '时间', recipient: '收件人', subject: '主题', body: '正文', status: '状态', noActivity: '该域名尚无发送活动', viewActivity: '查看发送活动', sent: '已发送', delivered: '已送达', queued: '已入队', deferred: '已延迟', failed: '失败', bounced: '退信',
|
|
|
- editTitle: '编辑域名配置', save: '保存', cancel: '取消', deleteTitle: '删除域名', deleteWarning: '此操作不可撤销,域名相关发送配置将立即不可用。', typeDomain: '请输入域名以确认:', deleteConfirmation: '域名删除确认', queueTest: '加入发送队列', validRecipient: '请输入有效的收件邮箱', testSubject: (domain: string) => `MailHub ${domain} 测试邮件`, testBody: (domain: string) => `这是一封来自 ${domain} 的 MailHub 投递测试邮件。`
|
|
|
+ editTitle: '编辑域名配置', save: '保存', cancel: '取消', deleteTitle: '删除域名', deleteWarning: '此操作不可撤销,域名相关发送配置将立即不可用;如仍有关联邮箱,系统会阻止删除。', typeDomain: '请输入域名以确认:', deleteConfirmation: '域名删除确认', queueTest: '加入发送队列', validRecipient: '请输入有效的收件邮箱', testSubject: (domain: string) => `MailHub ${domain} 测试邮件`, testBody: (domain: string) => `这是一封来自 ${domain} 的 MailHub 投递测试邮件。`
|
|
|
};
|
|
|
|
|
|
const enCopy: typeof zhCopy = {
|
|
|
@@ -516,7 +520,7 @@ const enCopy: typeof zhCopy = {
|
|
|
dnsPassed: 'DNS passed', needsAttention: 'Needs attention', testSend: 'Send test', checkDns: 'Check DNS', autoDns: 'Apply DNS', edit: 'Edit configuration', delete: 'Delete domain', checked: 'DNS check completed', dnsApplied: 'DNS records applied', dnsPartial: 'Some DNS records could not be applied', saved: 'Configuration saved', testQueued: 'Test email queued', deleted: 'Domain deleted', actionFailed: 'Action failed', copied: 'Copied', copyFailed: 'Unable to copy', setupPartialTitle: 'Domain created, but DNS follow-up needs attention', setupPartialDescription: 'Automatic application or the immediate check did not fully complete. Review each result below, correct it, and retry.', setupCompleteTitle: 'Domain created', setupAutomaticDescription: 'The automatic DNS flow ran. Review each result and the current verification status.', setupManualDescription: 'Add the records below at your DNS provider, then recheck after they propagate.',
|
|
|
domainSummary: 'Domain summary', authenticationHealth: 'Authentication health', recordsPassed: 'required records passed', ready: 'Ready to send', nextActions: 'Next actions', reviewDns: 'Review DNS records', dangerZone: 'Danger zone', dangerHint: 'Deleting the domain stops its sending configuration and cannot be undone.', recheck: 'Recheck', copyAll: 'Copy all', domainVerified: 'Domain verified and ready to send', domainNotVerified: 'Domain verification is incomplete', lastChecked: 'Last checked', never: 'Never', noDnsRecords: 'No DNS check results yet', checkNow: 'Check now', warnings: 'Attention needed', applyResults: 'DNS apply results', applyComplete: 'Complete', applyPartial: 'Partially failed', applySucceeded: 'Succeeded', applyFailed: 'Failed', applySkipped: 'Skipped', applySucceededHint: 'The record was written or already matched.', applyFailedHint: 'No detailed error was returned. Retry or configure this record manually.',
|
|
|
host: 'Host', ports: 'Ports', username: 'Username', password: 'Password', configured: 'Set (never returned in plaintext)', notConfigured: 'Not set', manageKeys: 'Manage API keys', availableKeys: 'Available keys', noKeys: 'No API keys available', senderIdentity: 'Sender identity', fromDomain: 'From domain', verification: 'Verification', verified: 'Verified', pending: 'Pending',
|
|
|
- inboundStatus: 'Inbound service', inboundEnabled: 'Inbound service enabled', inboundDisabled: 'Inbound service disabled', inboundHint: 'Manage mailboxes, aliases, and routes in the dedicated Inbox workspace.', openMailboxRouting: 'Open mailboxes & routing', mailboxes: 'Mailboxes', unread: 'unread', messages: 'messages', noMailboxes: 'No mailboxes for this domain', configureMailbox: 'Configure mailbox', catchAll: 'Default inbound route', catchAllAddress: 'Catch-all address', disabled: 'Disabled',
|
|
|
+ inboundStatus: 'Inbound service', inboundEnabled: 'Inbound service enabled', inboundDisabled: 'Inbound service disabled', inboundHint: 'Manage mailboxes, aliases, and routes in the dedicated Inbox workspace.', openMailboxRouting: 'Open mailboxes & routing', mailboxes: 'Mailboxes', unread: 'unread', messages: 'messages', noMailboxes: 'No mailboxes for this domain', configureMailbox: 'Configure mailbox', catchAll: 'Default inbound route', catchAllAddress: 'Catch-all address', mailboxSignupPolicy: 'Mailbox creation access', mailboxSignupOpen: 'Shared with other users', mailboxSignupOwnerOnly: 'Owner only', mailboxSignupHint: 'When enabled, other users can create their own mailboxes under this domain, but cannot manage the domain configuration.', mailboxSignupExtra: 'Sharing only allows mailbox creation. DNS, catch-all, deletion, and domain administration stay private to the owner.', enabled: 'Open', disabled: 'Disabled',
|
|
|
time: 'Time', recipient: 'Recipient', subject: 'Subject', body: 'Body', status: 'Status', noActivity: 'No sending activity for this domain', viewActivity: 'View sending activity', sent: 'Sent', delivered: 'Delivered', queued: 'Queued', deferred: 'Deferred', failed: 'Failed', bounced: 'Bounced',
|
|
|
- editTitle: 'Edit domain configuration', save: 'Save', cancel: 'Cancel', deleteTitle: 'Delete domain', deleteWarning: 'This cannot be undone. Sending configuration will stop immediately.', typeDomain: 'Type the domain to confirm:', deleteConfirmation: 'Domain deletion confirmation', queueTest: 'Queue test', validRecipient: 'Enter a valid recipient email', testSubject: (domain: string) => `MailHub ${domain} test email`, testBody: (domain: string) => `This is a MailHub delivery test from ${domain}.`
|
|
|
+ editTitle: 'Edit domain configuration', save: 'Save', cancel: 'Cancel', deleteTitle: 'Delete domain', deleteWarning: 'This cannot be undone. Sending configuration will stop immediately; deletion is blocked while mailboxes remain attached.', typeDomain: 'Type the domain to confirm:', deleteConfirmation: 'Domain deletion confirmation', queueTest: 'Queue test', validRecipient: 'Enter a valid recipient email', testSubject: (domain: string) => `MailHub ${domain} test email`, testBody: (domain: string) => `This is a MailHub delivery test from ${domain}.`
|
|
|
};
|