resend http api
This commit is contained in:
@@ -35,9 +35,6 @@ function serializeConfig(smtp: SmtpConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildTransportUrl(config: SmtpConfig): string {
|
function buildTransportUrl(config: SmtpConfig): string {
|
||||||
if (config.provider === 'resend') {
|
|
||||||
return `smtps://resend:${config.apiKey}@smtp.resend.com:465`;
|
|
||||||
}
|
|
||||||
if (config.provider === 'mailhog') {
|
if (config.provider === 'mailhog') {
|
||||||
return `smtp://${config.host ?? 'localhost'}:${config.port ?? 1025}`;
|
return `smtp://${config.host ?? 'localhost'}:${config.port ?? 1025}`;
|
||||||
}
|
}
|
||||||
@@ -72,8 +69,20 @@ smtpRouter.put('/', async (ctx) => {
|
|||||||
|
|
||||||
smtpRouter.post('/test-connection', async (ctx) => {
|
smtpRouter.post('/test-connection', async (ctx) => {
|
||||||
try {
|
try {
|
||||||
const { transport } = await getTransport();
|
const result = await getTransport();
|
||||||
await transport.verify();
|
|
||||||
|
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 });
|
return ctx.json({ success: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : 'Unknown error';
|
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;
|
if (body.password?.includes('****')) body.password = saved.password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const from = `${body.fromName} <${body.fromEmail}>`;
|
||||||
|
const testHtml = '<h2>Officer Test Email</h2><p>If you received this, your email configuration is working correctly.</p>';
|
||||||
|
|
||||||
try {
|
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 url = buildTransportUrl(body);
|
||||||
const transport = createTransport(url);
|
const transport = createTransport(url);
|
||||||
const from = `${body.fromName} <${body.fromEmail}>`;
|
await transport.sendMail({ from, to: body.to, subject: 'Officer Test Email', html: testHtml });
|
||||||
|
|
||||||
await transport.sendMail({
|
|
||||||
from,
|
|
||||||
to: body.to,
|
|
||||||
subject: 'Officer Test Email',
|
|
||||||
html: '<h2>Officer Test Email</h2><p>If you received this, your email configuration is working correctly.</p>',
|
|
||||||
});
|
|
||||||
return ctx.json({ success: true });
|
return ctx.json({ success: true });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : 'Unknown error';
|
const message = err instanceof Error ? err.message : 'Unknown error';
|
||||||
|
|||||||
@@ -11,14 +11,32 @@ type SendMailArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function sendMail({ template, subject, to, data, from }: SendMailArgs) {
|
export async function sendMail({ template, subject, to, data, from }: SendMailArgs) {
|
||||||
const { transport, from: configuredFrom } = await getTransport();
|
const result = await getTransport();
|
||||||
const sender = from ?? configuredFrom ?? 'officerdev <no-reply@officer.dev>';
|
const sender = from ?? result.from ?? 'officerdev <no-reply@officer.dev>';
|
||||||
|
|
||||||
const theTemplate = templates[template];
|
const theTemplate = templates[template];
|
||||||
const templated = theTemplate(data);
|
const templated = theTemplate(data);
|
||||||
|
|
||||||
const emailHtml = await render(templated);
|
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 { getTransport, templates };
|
||||||
|
export type { TransportResult } from './transport';
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ let cachedTransport: Transporter | null = null;
|
|||||||
let cachedConfigHash: string | null = null;
|
let cachedConfigHash: string | null = null;
|
||||||
|
|
||||||
function buildTransportUrl(config: SmtpConfig): string {
|
function buildTransportUrl(config: SmtpConfig): string {
|
||||||
if (config.provider === 'resend') {
|
|
||||||
return `smtps://resend:${config.apiKey}@smtp.resend.com:465`;
|
|
||||||
}
|
|
||||||
if (config.provider === 'mailhog') {
|
if (config.provider === 'mailhog') {
|
||||||
return `smtp://${config.host ?? 'localhost'}:${config.port ?? 1025}`;
|
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}`;
|
return `${protocol}://${auth}${config.host}:${config.port ?? 587}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TransportResult = {
|
type SmtpTransport = { type: 'smtp'; transport: Transporter; from: string | null };
|
||||||
transport: Transporter;
|
type ResendTransport = { type: 'resend'; apiKey: string; from: string };
|
||||||
from: string | null;
|
export type TransportResult = SmtpTransport | ResendTransport;
|
||||||
};
|
|
||||||
|
|
||||||
export async function getTransport(): Promise<TransportResult> {
|
export async function getTransport(): Promise<TransportResult> {
|
||||||
try {
|
try {
|
||||||
@@ -44,14 +40,20 @@ export async function getTransport(): Promise<TransportResult> {
|
|||||||
const settings = await file.json();
|
const settings = await file.json();
|
||||||
const smtp: SmtpConfig | undefined = settings.smtp;
|
const smtp: SmtpConfig | undefined = settings.smtp;
|
||||||
if (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);
|
const hash = JSON.stringify(smtp);
|
||||||
if (cachedTransport && cachedConfigHash === hash) {
|
if (cachedTransport && cachedConfigHash === hash) {
|
||||||
return { transport: cachedTransport, from: `${smtp.fromName} <${smtp.fromEmail}>` };
|
return { type: 'smtp', transport: cachedTransport, from };
|
||||||
}
|
}
|
||||||
const url = buildTransportUrl(smtp);
|
const url = buildTransportUrl(smtp);
|
||||||
cachedTransport = createTransport(url);
|
cachedTransport = createTransport(url);
|
||||||
cachedConfigHash = hash;
|
cachedConfigHash = hash;
|
||||||
return { transport: cachedTransport, from: `${smtp.fromName} <${smtp.fromEmail}>` };
|
return { type: 'smtp', transport: cachedTransport, from };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -63,7 +65,7 @@ export async function getTransport(): Promise<TransportResult> {
|
|||||||
cachedTransport = createTransport(MAIL_TRANSPORT);
|
cachedTransport = createTransport(MAIL_TRANSPORT);
|
||||||
cachedConfigHash = 'env';
|
cachedConfigHash = 'env';
|
||||||
}
|
}
|
||||||
return { transport: cachedTransport, from: null };
|
return { type: 'smtp', transport: cachedTransport, from: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('No email transport configured');
|
throw new Error('No email transport configured');
|
||||||
|
|||||||
Reference in New Issue
Block a user