All files / lib/email/providers resend.provider.ts

100% Statements 14/14
100% Branches 8/8
100% Functions 4/4
100% Lines 12/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68                                5x             53x 53x               57x   57x                       57x 21x     21x       21x         36x       36x 36x      
import type { EmailConfig, EmailProvider } from "./base";
 
/**
 * Resend email provider for production.
 *
 * Calls the REST API directly rather than through the `resend` SDK. The SDK is
 * a thin client over this one POST, but it depends on `svix` (webhook signature
 * verification, 256 KB) and `postal-mime` (inbound MIME parsing, 132 KB), and it
 * re-exports `@react-email/render` — so importing it to send an email pulled a
 * webhook verifier, a MIME parser and a React renderer into module evaluation on
 * every cold isolate. This API never receives a Resend webhook and never parses
 * MIME; it only sends.
 *
 * Same shape as MailtrapProvider and MailpitProvider, which have always used
 * fetch. API: https://api.resend.com/emails
 */
const RESEND_API_URL = "https://api.resend.com/emails";
 
export class ResendProvider implements EmailProvider {
	private apiKey: string;
	private from: string;
 
	constructor(apiKey: string, config: EmailConfig) {
		this.apiKey = apiKey;
		this.from = config.from;
	}
 
	async sendEmail(params: {
		to: string;
		subject: string;
		html: string;
	}): Promise<{ id: string }> {
		const { to, subject, html } = params;
 
		const response = await fetch(RESEND_API_URL, {
			method: "POST",
			headers: {
				Authorization: `Bearer ${this.apiKey}`,
				"Content-Type": "application/json",
			},
			body: JSON.stringify({ from: this.from, to, subject, html }),
		});
 
		// Resend reports failures in the body as { message, name, statusCode }.
		// Read it before trusting the status, and fall back to the status line
		// when the body is not the JSON we expect (a gateway error page, say).
		if (!response.ok) {
			const error = (await response.json().catch(() => null)) as {
				message?: string;
			} | null;
			console.error(
				"[EMAIL-RESEND] Failed to send:",
				error ?? response.statusText,
			);
			throw new Error(
				`Failed to send email: ${error?.message ?? `${response.status} ${response.statusText}`}`,
			);
		}
 
		const data = (await response.json().catch(() => null)) as {
			id?: string | null;
		} | null;
 
		console.log(`[EMAIL-RESEND] Email sent to ${to}: ${subject}`);
		return { id: data?.id || "unknown" };
	}
}