feat(email): round-trip test — send via SMTP to the IMAP mailbox and confirm arrival

- emailIntakeService.roundTripTest(): sends a uniquely-tagged email through the
  saved SMTP config to the IMAP mailbox (imap_user), then polls IMAP up to 30s
  for that subject token; deletes the test message on arrival so it never hits
  the accounting inbox. Returns {ok, seconds, recipient} or a typed reason.
- route POST /admin/email/incoming-config/roundtrip (email.send)
- IMAP card: 'Round-trip test' button beside 'Test connection' + Save; toast
  reports recipient + delivery time. Distinct reasons mapped (smtp/imap
  unconfigured, send_failed, not_received→504).
- en/de strings
This commit is contained in:
Luca
2026-06-12 14:52:14 +02:00
parent f017649bd5
commit 04be51a008
6 changed files with 134 additions and 4 deletions
+12
View File
@@ -102,6 +102,12 @@ export interface ImapTestResult {
unseen: number;
}
export interface ImapRoundTripResult {
ok: boolean;
seconds?: number;
recipient?: string;
}
export interface ReceivedEmail {
id: number;
message_id: string | null;
@@ -150,6 +156,12 @@ export const emailService = {
const response = await api.post<ImapTestResult>('/admin/email/incoming-config/test', config || {});
return response.data;
},
// End-to-end: send via SMTP to the IMAP mailbox and confirm it arrives.
// Uses saved config for both sides — no body. May take up to ~30s.
async roundTripIncoming(): Promise<ImapRoundTripResult> {
const response = await api.post<ImapRoundTripResult>('/admin/email/incoming-config/roundtrip', {});
return response.data;
},
async listReceived(params: { page?: number; pageSize?: number } = {}): Promise<ReceivedEmailsResponse> {
const response = await api.get<ReceivedEmailsResponse>('/admin/email/received', { params });
return response.data;