Add DKIM selector fields for value-only Email DNS pastes

Let operators paste bare v=DKIM1 TXT values and enter selectors
separately; keep full Stalwart zone-line paste working.
This commit is contained in:
Alireza Hassani
2026-09-18 10:38:31 +03:30
parent eedaac3a71
commit 29d1efce0f
3 changed files with 92 additions and 7 deletions
@@ -12,6 +12,30 @@
color: var(--text-primary);
}
.selectorInput {
width: 100%;
min-height: var(--field-height);
padding: var(--field-padding-y) var(--field-padding-x);
font-size: var(--field-font-size);
font-family: var(--font-ui);
line-height: 1.4;
color: var(--text-primary);
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(148, 163, 184, 0.35);
border-radius: var(--radius-sm);
outline: none;
box-sizing: border-box;
}
.selectorInput:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(var(--primary-rgb) / 0.12);
}
.selectorInput:disabled {
opacity: 0.65;
}
.textarea {
width: 100%;
min-height: 110px;
@@ -14,10 +14,22 @@ interface EmailDnsModalProps {
onClose: () => void
}
function pasteIncludesDomainkey(paste: string) {
return /[a-z0-9][a-z0-9-]*\._domainkey/i.test(paste)
}
function keyReady(paste: string, selector: string) {
const trimmed = paste.trim()
if (!trimmed) return false
return pasteIncludesDomainkey(trimmed) || Boolean(selector.trim())
}
export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) {
const { showToast } = useToast()
const [dnsProvider, setDnsProvider] = useState<ParkedDnsProvider>('arvan')
const [ed25519Selector, setEd25519Selector] = useState('')
const [ed25519Key, setEd25519Key] = useState('')
const [rsaSelector, setRsaSelector] = useState('')
const [rsaKey, setRsaKey] = useState('')
const [submitting, setSubmitting] = useState(false)
const [error, setError] = useState('')
@@ -25,7 +37,9 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) {
useEffect(() => {
if (!domain) return
setDnsProvider('arvan')
setEd25519Selector('')
setEd25519Key('')
setRsaSelector('')
setRsaKey('')
setError('')
setSubmitting(false)
@@ -33,6 +47,8 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) {
if (!domain) return null
const target = domain
const canSubmit =
keyReady(ed25519Key, ed25519Selector) && keyReady(rsaKey, rsaSelector)
async function submit() {
setSubmitting(true)
@@ -42,6 +58,10 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) {
dnsProvider,
ed25519Key,
rsaKey,
...(ed25519Selector.trim()
? { ed25519Selector: ed25519Selector.trim() }
: {}),
...(rsaSelector.trim() ? { rsaSelector: rsaSelector.trim() } : {}),
})
showToast(result.message, 'success')
onClose()
@@ -65,8 +85,9 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) {
}}
>
<p className={parkedStyles.hint}>
Paste the two DKIM lines from Stalwart (Directory → Domains → ⋮ → View DNS
zone file). We upsert mail records on{' '}
Paste each DKIM <strong>TXT value</strong> (<code>v=DKIM1; …</code>) from
Stalwart, or the full zone line. For value-only pastes, fill the selector
(for example <code>v1-ed25519-20260901</code>). We upsert mail records on{' '}
{dnsProvider === 'cloudflare' ? 'Cloudflare' : 'Arvan'} (DNS-only / not
proxied): <strong>mail</strong> A → 185.214.101.41, MX → mail.meshkee.com,
SPF, both DKIM keys, autoconfig / autodiscover. Extra MX,{' '}
@@ -92,6 +113,25 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) {
/>
</div>
<div className={tableStyles.field}>
<label htmlFor="mail-dkim-ed25519-selector">Ed25519 selector</label>
<input
id="mail-dkim-ed25519-selector"
className={styles.selectorInput}
type="text"
value={ed25519Selector}
onChange={(e) => setEd25519Selector(e.target.value)}
placeholder="v1-ed25519-20260901"
disabled={submitting}
spellCheck={false}
dir="ltr"
autoComplete="off"
/>
<p className={tableStyles.helperText}>
Needed when pasting only the TXT value. Optional if the paste includes{' '}
<code>…._domainkey</code>.
</p>
</div>
<div className={tableStyles.field}>
<label htmlFor="mail-dkim-ed25519">Ed25519 domain key</label>
<textarea
@@ -99,14 +139,33 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) {
className={styles.textarea}
value={ed25519Key}
onChange={(e) => setEd25519Key(e.target.value)}
placeholder="v1-ed25519-…. _domainkey IN TXT &quot;v=DKIM1; k=ed25519; …&quot;"
placeholder='v=DKIM1; k=ed25519; h=sha256; p=…'
disabled={submitting}
spellCheck={false}
dir="ltr"
rows={5}
rows={4}
/>
</div>
<div style={{ height: 12 }} />
<div className={tableStyles.field}>
<label htmlFor="mail-dkim-rsa-selector">RSA selector</label>
<input
id="mail-dkim-rsa-selector"
className={styles.selectorInput}
type="text"
value={rsaSelector}
onChange={(e) => setRsaSelector(e.target.value)}
placeholder="v1-rsa-20260901"
disabled={submitting}
spellCheck={false}
dir="ltr"
autoComplete="off"
/>
<p className={tableStyles.helperText}>
Needed when pasting only the TXT value. Optional if the paste includes{' '}
<code>…._domainkey</code>.
</p>
</div>
<div className={tableStyles.field}>
<label htmlFor="mail-dkim-rsa">RSA domain key</label>
<textarea
@@ -114,11 +173,11 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) {
className={styles.textarea}
value={rsaKey}
onChange={(e) => setRsaKey(e.target.value)}
placeholder="v1-rsa-…. _domainkey IN TXT &quot;v=DKIM1; k=rsa; …&quot;"
placeholder='v=DKIM1; k=rsa; h=sha256; p=…'
disabled={submitting}
spellCheck={false}
dir="ltr"
rows={7}
rows={6}
/>
</div>
@@ -136,7 +195,7 @@ export function EmailDnsModal({ domain, onClose }: EmailDnsModalProps) {
type="button"
className={`${tableStyles.btn} ${tableStyles.btnPrimary}`}
onClick={() => void submit()}
disabled={submitting || !ed25519Key.trim() || !rsaKey.trim()}
disabled={submitting || !canSubmit}
>
Update DNS
</button>
@@ -114,6 +114,8 @@ export async function applyMailDns(
dnsProvider: 'arvan' | 'cloudflare'
ed25519Key: string
rsaKey: string
ed25519Selector?: string
rsaSelector?: string
},
) {
return apiRequest<ApplyMailDnsResponse>(`/domains/${domainId}/mail-dns`, {