From e95c5de70d1315e4114e53be21bb024438ae4c1a Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Tue, 24 Feb 2026 23:29:41 +0000 Subject: [PATCH] resend http api --- src/servers/api/server-settings/smtp.ts | 47 ++++++++++++++++++------- src/workspaces/emailer/src/index.ts | 24 +++++++++++-- src/workspaces/emailer/src/transport.ts | 22 ++++++------ 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/src/servers/api/server-settings/smtp.ts b/src/servers/api/server-settings/smtp.ts index c5425787..fc7bcc91 100644 --- a/src/servers/api/server-settings/smtp.ts +++ b/src/servers/api/server-settings/smtp.ts @@ -35,9 +35,6 @@ function serializeConfig(smtp: SmtpConfig) { } function buildTransportUrl(config: SmtpConfig): string { - if (config.provider === 'resend') { - return `smtps://resend:${config.apiKey}@smtp.resend.com:465`; - } if (config.provider === 'mailhog') { return `smtp://${config.host ?? 'localhost'}:${config.port ?? 1025}`; } @@ -72,8 +69,20 @@ smtpRouter.put('/', async (ctx) => { smtpRouter.post('/test-connection', async (ctx) => { try { - const { transport } = await getTransport(); - await transport.verify(); + const result = await getTransport(); + + if (result.type === 'resend') { + const res = await fetch('https://api.resend.com/domains', { + headers: { 'Authorization': `Bearer ${result.apiKey}` }, + }); + if (!res.ok) { + const err = await res.json(); + throw new Error(err.message ?? `Resend API error: ${res.status}`); + } + return ctx.json({ success: true }); + } + + await result.transport.verify(); return ctx.json({ success: true }); } catch (err) { const message = err instanceof Error ? err.message : 'Unknown error'; @@ -93,17 +102,29 @@ smtpRouter.post('/test', async (ctx) => { if (body.password?.includes('****')) body.password = saved.password; } + const from = `${body.fromName} <${body.fromEmail}>`; + const testHtml = '

Officer Test Email

If you received this, your email configuration is working correctly.

'; + try { + if (body.provider === 'resend') { + const res = await fetch('https://api.resend.com/emails', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${body.apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ from, to: body.to, subject: 'Officer Test Email', html: testHtml }), + }); + if (!res.ok) { + const err = await res.json(); + throw new Error(err.message ?? `Resend API error: ${res.status}`); + } + return ctx.json({ success: true }); + } + const url = buildTransportUrl(body); const transport = createTransport(url); - const from = `${body.fromName} <${body.fromEmail}>`; - - await transport.sendMail({ - from, - to: body.to, - subject: 'Officer Test Email', - html: '

Officer Test Email

If you received this, your email configuration is working correctly.

', - }); + await transport.sendMail({ from, to: body.to, subject: 'Officer Test Email', html: testHtml }); return ctx.json({ success: true }); } catch (err) { const message = err instanceof Error ? err.message : 'Unknown error'; diff --git a/src/workspaces/emailer/src/index.ts b/src/workspaces/emailer/src/index.ts index 5c60c681..fc58293b 100644 --- a/src/workspaces/emailer/src/index.ts +++ b/src/workspaces/emailer/src/index.ts @@ -11,14 +11,32 @@ type SendMailArgs = { }; export async function sendMail({ template, subject, to, data, from }: SendMailArgs) { - const { transport, from: configuredFrom } = await getTransport(); - const sender = from ?? configuredFrom ?? 'officerdev '; + const result = await getTransport(); + const sender = from ?? result.from ?? 'officerdev '; const theTemplate = templates[template]; const templated = theTemplate(data); const emailHtml = await render(templated); - await transport.sendMail({ from: sender, to, subject, html: emailHtml }); + + if (result.type === 'resend') { + const res = await fetch('https://api.resend.com/emails', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${result.apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ from: sender, to, subject, html: emailHtml }), + }); + if (!res.ok) { + const err = await res.json(); + throw new Error(err.message ?? `Resend API error: ${res.status}`); + } + return; + } + + await result.transport.sendMail({ from: sender, to, subject, html: emailHtml }); } export { getTransport, templates }; +export type { TransportResult } from './transport'; diff --git a/src/workspaces/emailer/src/transport.ts b/src/workspaces/emailer/src/transport.ts index 8220108b..94a1af3c 100644 --- a/src/workspaces/emailer/src/transport.ts +++ b/src/workspaces/emailer/src/transport.ts @@ -21,9 +21,6 @@ let cachedTransport: Transporter | null = null; let cachedConfigHash: string | null = null; function buildTransportUrl(config: SmtpConfig): string { - if (config.provider === 'resend') { - return `smtps://resend:${config.apiKey}@smtp.resend.com:465`; - } if (config.provider === 'mailhog') { return `smtp://${config.host ?? 'localhost'}:${config.port ?? 1025}`; } @@ -32,10 +29,9 @@ function buildTransportUrl(config: SmtpConfig): string { return `${protocol}://${auth}${config.host}:${config.port ?? 587}`; } -type TransportResult = { - transport: Transporter; - from: string | null; -}; +type SmtpTransport = { type: 'smtp'; transport: Transporter; from: string | null }; +type ResendTransport = { type: 'resend'; apiKey: string; from: string }; +export type TransportResult = SmtpTransport | ResendTransport; export async function getTransport(): Promise { try { @@ -44,14 +40,20 @@ export async function getTransport(): Promise { const settings = await file.json(); const smtp: SmtpConfig | undefined = settings.smtp; if (smtp) { + const from = `${smtp.fromName} <${smtp.fromEmail}>`; + + if (smtp.provider === 'resend') { + return { type: 'resend', apiKey: smtp.apiKey ?? '', from }; + } + const hash = JSON.stringify(smtp); if (cachedTransport && cachedConfigHash === hash) { - return { transport: cachedTransport, from: `${smtp.fromName} <${smtp.fromEmail}>` }; + return { type: 'smtp', transport: cachedTransport, from }; } const url = buildTransportUrl(smtp); cachedTransport = createTransport(url); cachedConfigHash = hash; - return { transport: cachedTransport, from: `${smtp.fromName} <${smtp.fromEmail}>` }; + return { type: 'smtp', transport: cachedTransport, from }; } } } catch { @@ -63,7 +65,7 @@ export async function getTransport(): Promise { cachedTransport = createTransport(MAIL_TRANSPORT); cachedConfigHash = 'env'; } - return { transport: cachedTransport, from: null }; + return { type: 'smtp', transport: cachedTransport, from: null }; } throw new Error('No email transport configured');