Merge pull request #222 from christianlouis/copilot/fix-apprise-smtp-channel
fix(notifications): add separate sender, recipient, and SMTP username fields to email channel
This commit is contained in:
@@ -79,10 +79,12 @@ describe('NotificationWizard', () => {
|
|||||||
|
|
||||||
it('should show Email fields', () => {
|
it('should show Email fields', () => {
|
||||||
goToStep2('Email');
|
goToStep2('Email');
|
||||||
expect(screen.getByText('Username / Email')).toBeInTheDocument();
|
expect(screen.getByText('SMTP Username')).toBeInTheDocument();
|
||||||
expect(screen.getByText('SMTP Password')).toBeInTheDocument();
|
expect(screen.getByText('SMTP Password')).toBeInTheDocument();
|
||||||
expect(screen.getByText('SMTP Host')).toBeInTheDocument();
|
expect(screen.getByText('SMTP Host')).toBeInTheDocument();
|
||||||
expect(screen.getByText('SMTP Port')).toBeInTheDocument();
|
expect(screen.getByText('SMTP Port')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Sender Email (From)')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Recipient Email (To)')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show Webhook fields', () => {
|
it('should show Webhook fields', () => {
|
||||||
@@ -294,6 +296,72 @@ describe('NotificationWizard', () => {
|
|||||||
});
|
});
|
||||||
expect(url).toBe('tgram://mybot/mychat/');
|
expect(url).toBe('tgram://mybot/mychat/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should build correct Email URL with recipient only', () => {
|
||||||
|
render(<NotificationWizard onComplete={mockOnComplete} onCancel={mockOnCancel} />);
|
||||||
|
fireEvent.click(screen.getByText('Email'));
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('user@example.com or API key'), {
|
||||||
|
target: { value: 'apikey' },
|
||||||
|
});
|
||||||
|
fireEvent.change(document.querySelector('input[type="password"]')!, {
|
||||||
|
target: { value: 'secret' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('smtp.example.com'), {
|
||||||
|
target: { value: 'smtp.postmarkapp.com' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('587'), {
|
||||||
|
target: { value: '587' },
|
||||||
|
});
|
||||||
|
// sender left blank (optional)
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('you@example.com'), {
|
||||||
|
target: { value: 'me@example.com' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Next').closest('button')!);
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('e.g. My Telegram Alert'), {
|
||||||
|
target: { value: 'Postmark' },
|
||||||
|
});
|
||||||
|
fireEvent.click(screen.getByText('Save Channel').closest('button')!);
|
||||||
|
|
||||||
|
const url = mockOnComplete.mock.calls[0][0].apprise_url as string;
|
||||||
|
expect(url).toContain('mailto://apikey:secret@smtp.postmarkapp.com:587/');
|
||||||
|
expect(url).toContain('to=me%40example.com');
|
||||||
|
expect(url).not.toContain('from=');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should build correct Email URL with both sender and recipient', () => {
|
||||||
|
render(<NotificationWizard onComplete={mockOnComplete} onCancel={mockOnCancel} />);
|
||||||
|
fireEvent.click(screen.getByText('Email'));
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('user@example.com or API key'), {
|
||||||
|
target: { value: 'apikey' },
|
||||||
|
});
|
||||||
|
fireEvent.change(document.querySelector('input[type="password"]')!, {
|
||||||
|
target: { value: 'secret' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('smtp.example.com'), {
|
||||||
|
target: { value: 'smtp.postmarkapp.com' },
|
||||||
|
});
|
||||||
|
// port left blank (optional)
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('noreply@example.com'), {
|
||||||
|
target: { value: 'noreply@example.com' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('you@example.com'), {
|
||||||
|
target: { value: 'me@example.com' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Next').closest('button')!);
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('e.g. My Telegram Alert'), {
|
||||||
|
target: { value: 'Postmark' },
|
||||||
|
});
|
||||||
|
fireEvent.click(screen.getByText('Save Channel').closest('button')!);
|
||||||
|
|
||||||
|
const url = mockOnComplete.mock.calls[0][0].apprise_url as string;
|
||||||
|
expect(url).toContain('mailto://apikey:secret@smtp.postmarkapp.com/');
|
||||||
|
expect(url).toContain('to=me%40example.com');
|
||||||
|
expect(url).toContain('from=noreply%40example.com');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Edit mode (initialData) ────────────────────────────────────────────
|
// ── Edit mode (initialData) ────────────────────────────────────────────
|
||||||
|
|||||||
@@ -71,10 +71,28 @@ const CHANNEL_FIELDS: Record<string, ChannelField[]> = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
email: [
|
email: [
|
||||||
{ key: 'username', label: 'Username / Email', placeholder: 'user@example.com' },
|
{
|
||||||
|
key: 'username',
|
||||||
|
label: 'SMTP Username',
|
||||||
|
placeholder: 'user@example.com or API key',
|
||||||
|
hint: 'SMTP authentication username — can be an API key (e.g. Postmark)',
|
||||||
|
},
|
||||||
{ key: 'password', label: 'SMTP Password', placeholder: '••••••••', type: 'password' },
|
{ key: 'password', label: 'SMTP Password', placeholder: '••••••••', type: 'password' },
|
||||||
{ key: 'host', label: 'SMTP Host', placeholder: 'smtp.example.com' },
|
{ key: 'host', label: 'SMTP Host', placeholder: 'smtp.example.com' },
|
||||||
{ key: 'port', label: 'SMTP Port', placeholder: '587', optional: true },
|
{ key: 'port', label: 'SMTP Port', placeholder: '587', optional: true },
|
||||||
|
{
|
||||||
|
key: 'sender',
|
||||||
|
label: 'Sender Email (From)',
|
||||||
|
placeholder: 'noreply@example.com',
|
||||||
|
hint: 'Email address shown as the sender. Defaults to SMTP Username if blank.',
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'recipient',
|
||||||
|
label: 'Recipient Email (To)',
|
||||||
|
placeholder: 'you@example.com',
|
||||||
|
hint: 'Email address where notifications are delivered.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
webhook: [
|
webhook: [
|
||||||
{
|
{
|
||||||
@@ -113,11 +131,15 @@ function buildAppriseUrl(channel: string, fields: Record<string, string>): strin
|
|||||||
if (match) return `slack://${match[1]}/${match[2]}/${match[3]}/`;
|
if (match) return `slack://${match[1]}/${match[2]}/${match[3]}/`;
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
case 'email':
|
case 'email': {
|
||||||
if (!fields.username || !fields.password || !fields.host) return '';
|
if (!fields.username || !fields.password || !fields.host || !fields.recipient) return '';
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.set('to', fields.recipient);
|
||||||
|
if (fields.sender) params.set('from', fields.sender);
|
||||||
return `mailto://${encodeURIComponent(fields.username)}:${encodeURIComponent(fields.password)}@${fields.host}${
|
return `mailto://${encodeURIComponent(fields.username)}:${encodeURIComponent(fields.password)}@${fields.host}${
|
||||||
fields.port ? `:${fields.port}` : ''
|
fields.port ? `:${fields.port}` : ''
|
||||||
}`;
|
}/?${params.toString()}`;
|
||||||
|
}
|
||||||
case 'webhook':
|
case 'webhook':
|
||||||
return fields.url || '';
|
return fields.url || '';
|
||||||
case 'custom':
|
case 'custom':
|
||||||
|
|||||||
Reference in New Issue
Block a user