fix: improve SMTP configuration and error handling
Some checks failed
CI / backend (push) Failing after 17s
CI / frontend (push) Successful in 31s
CI / docker (push) Has been skipped

- Change default use_tls from True to False to match typical STARTTLS setup
- Add MailDeliveryError exception for mail delivery failures
- Wrap send_mail calls in try-catch blocks to handle errors gracefully
- Return 502 status code with error details when mail delivery fails
- Add SMTP security mode selector in frontend (STARTTLS/TLS/None)
- Add test mail form to admin panel
- Handle empty SMTP credentials properly in update_mail_settings
- Catch
This commit is contained in:
2026-06-04 11:00:11 +02:00
parent 15d47d49bf
commit 5ed613d441
6 changed files with 59 additions and 17 deletions

View File

@@ -61,6 +61,10 @@ export const dictionaries: Record<Language, Record<string, string>> = {
smtpPort: 'SMTP Port',
smtpUser: 'SMTP User',
smtpPassword: 'SMTP Passwort',
smtpSecurity: 'SMTP Sicherheit',
smtpStarttls: 'STARTTLS (typisch Port 587)',
smtpTls: 'TLS/SSL (typisch Port 465)',
smtpNone: 'Keine Verschlüsselung',
senderAddress: 'Absender-Adresse',
senderName: 'Absender-Name',
testMail: 'Testmail senden',
@@ -128,6 +132,10 @@ export const dictionaries: Record<Language, Record<string, string>> = {
smtpPort: 'SMTP port',
smtpUser: 'SMTP user',
smtpPassword: 'SMTP password',
smtpSecurity: 'SMTP security',
smtpStarttls: 'STARTTLS (usually port 587)',
smtpTls: 'TLS/SSL (usually port 465)',
smtpNone: 'No encryption',
senderAddress: 'Sender address',
senderName: 'Sender name',
testMail: 'Send test mail',

View File

@@ -61,8 +61,15 @@ function AdminUsers() {
function AdminMail() {
const { t } = useI18n();
const [form, setForm] = useState({ smtp_host: '', smtp_port: 587, smtp_user: '', smtp_password: '', sender_address: '', sender_name: 'NexaPantry', use_tls: true, use_starttls: true });
const [form, setForm] = useState({ smtp_host: '', smtp_port: 587, smtp_user: '', smtp_password: '', sender_address: '', sender_name: 'NexaPantry', use_tls: false, use_starttls: true });
const [testTo, setTestTo] = useState('');
const set = (key: string, value: string | number | boolean) => setForm((current) => ({ ...current, [key]: value }));
const security = form.use_starttls ? 'starttls' : form.use_tls ? 'tls' : 'none';
const setSecurity = (value: string) => setForm((current) => ({
...current,
use_starttls: value === 'starttls',
use_tls: value === 'tls'
}));
useEffect(() => { void api<Record<string, string | number | boolean | null>>('/admin/mail').then((data) => setForm((current) => ({ ...current, ...data, smtp_password: '' }))); }, []);
return (
<Panel title={t('mail')}>
@@ -71,10 +78,19 @@ function AdminMail() {
<Field label={t('smtpPort')} type="number" value={form.smtp_port} onChange={(e) => set('smtp_port', Number(e.target.value))} />
<Field label={t('smtpUser')} value={form.smtp_user} onChange={(e) => set('smtp_user', e.target.value)} />
<Field label={t('smtpPassword')} type="password" value={form.smtp_password} onChange={(e) => set('smtp_password', e.target.value)} />
<SelectField label={t('smtpSecurity')} value={security} onChange={(e) => setSecurity(e.target.value)}>
<option value="starttls">{t('smtpStarttls')}</option>
<option value="tls">{t('smtpTls')}</option>
<option value="none">{t('smtpNone')}</option>
</SelectField>
<Field label={t('senderAddress')} type="email" value={form.sender_address} onChange={(e) => set('sender_address', e.target.value)} />
<Field label={t('senderName')} value={form.sender_name} onChange={(e) => set('sender_name', e.target.value)} />
<Button>{t('save')}</Button>
</form>
<form className="mt-4 flex flex-col gap-3 sm:flex-row" onSubmit={async (event) => { event.preventDefault(); await api('/admin/mail/test', { method: 'POST', body: JSON.stringify({ to: testTo }) }); }}>
<Field className="sm:min-w-80" label={t('email')} type="email" required value={testTo} onChange={(e) => setTestTo(e.target.value)} />
<Button className="self-end" variant="secondary">{t('testMail')}</Button>
</form>
</Panel>
);
}