Add per-reply from-address selector and inbox address badges
- Reply composer shows a From dropdown (hello@ or nate@), defaulting to whichever address the inbound email was sent to - Server validates the chosen address against an allowlist before sending - Inbox list shows colored address badges: blue for hello@, purple for nate@ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+32
-3
@@ -637,6 +637,7 @@ interface ContactReplyDraft {
|
||||
recipientEmail: string
|
||||
subject: string
|
||||
message: string
|
||||
fromAddress: string
|
||||
}
|
||||
|
||||
interface ContactReplyTemplate {
|
||||
@@ -2751,12 +2752,18 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
return match ? `Re: ${match[1].trim()}` : 'Re: Your message'
|
||||
})()
|
||||
: 'Thanks for reaching out to Verse by Verse with Nate'
|
||||
// Default reply-from to whichever address the email was sent to
|
||||
const inboundTo = submission.inboundTo ?? ''
|
||||
const defaultFrom = inboundTo.includes('nate@')
|
||||
? 'Verse by Verse with Nate <nate@versebyversewithnate.us>'
|
||||
: 'Verse by Verse with Nate <hello@versebyversewithnate.us>'
|
||||
setContactReplyDraft({
|
||||
submissionId: submission.id,
|
||||
recipientName: submission.name,
|
||||
recipientEmail: submission.email,
|
||||
subject: subjectLine,
|
||||
message: '',
|
||||
fromAddress: defaultFrom,
|
||||
})
|
||||
setContactReplyStatus('idle')
|
||||
setContactReplyMsg(`Composing a reply to ${firstName}.`)
|
||||
@@ -2827,6 +2834,7 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
body: JSON.stringify({
|
||||
subject: contactReplyDraft.subject,
|
||||
message: contactReplyDraft.message,
|
||||
fromAddress: contactReplyDraft.fromAddress,
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -2836,7 +2844,8 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
}
|
||||
|
||||
setContactReplyStatus('sent')
|
||||
setContactReplyMsg(`Reply sent to ${contactReplyDraft.recipientEmail} from hello@versebyversewithnate.us.`)
|
||||
const sentFrom = contactReplyDraft.fromAddress.match(/<([^>]+)>/)?.[1] ?? contactReplyDraft.fromAddress
|
||||
setContactReplyMsg(`Reply sent to ${contactReplyDraft.recipientEmail} from ${sentFrom}.`)
|
||||
await reloadContactReplyHistory()
|
||||
setContactReplyDraft(null)
|
||||
} catch (err) {
|
||||
@@ -3312,7 +3321,20 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
<td><a href={`mailto:${c.email}`}>{c.email}</a></td>
|
||||
<td>
|
||||
<span className="admin-badge admin-badge--pending" style={{ fontSize: '0.7rem' }}>{c.messageType ?? 'contact'}</span>
|
||||
{c.source === 'inbound-email' && <span className="admin-badge admin-badge--info" style={{ fontSize: '0.7rem', marginLeft: '0.3rem' }} title={c.inboundTo ? `To: ${c.inboundTo}` : 'Direct email'}>email</span>}
|
||||
{c.source === 'inbound-email' && (
|
||||
<span
|
||||
className="admin-badge"
|
||||
style={{
|
||||
fontSize: '0.7rem',
|
||||
marginLeft: '0.3rem',
|
||||
background: c.inboundTo?.includes('nate@') ? '#7c3aed' : '#0369a1',
|
||||
color: '#fff',
|
||||
}}
|
||||
title={c.inboundTo ?? 'Direct email'}
|
||||
>
|
||||
{c.inboundTo?.includes('nate@') ? 'nate@' : 'hello@'}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td style={{ textAlign: 'center' }}>{c.subscribe ? '✓' : ''}</td>
|
||||
<td style={{ whiteSpace: 'nowrap' }}>{formatDate(c.submittedAt)}</td>
|
||||
@@ -5123,7 +5145,14 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
|
||||
</div>
|
||||
<div className="admin-field">
|
||||
<label htmlFor="reply-from">From</label>
|
||||
<input id="reply-from" type="text" value="hello@versebyversewithnate.us" readOnly />
|
||||
<select
|
||||
id="reply-from"
|
||||
value={contactReplyDraft.fromAddress}
|
||||
onChange={e => setContactReplyDraft(draft => draft ? { ...draft, fromAddress: e.target.value } : draft)}
|
||||
>
|
||||
<option value="Verse by Verse with Nate <hello@versebyversewithnate.us>">hello@versebyversewithnate.us</option>
|
||||
<option value="Verse by Verse with Nate <nate@versebyversewithnate.us>">nate@versebyversewithnate.us</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="admin-field">
|
||||
<label htmlFor="reply-subject">Subject</label>
|
||||
|
||||
Reference in New Issue
Block a user