@raypx/email
Email sending with Resend and React Email templates.
@raypx/email provides email sending capabilities with pluggable transports and React Email templates. It separates the rendering of email templates from the transport mechanism, allowing the same template to be sent via Resend in production or logged to the console in development.
Transports
Three transports are available:
| Transport | Use Case |
|---|---|
createResendTransport(options) | Production -- sends emails via Resend |
createLoggerTransport(options?) | Development -- logs email payloads using @raypx/logger |
createNoopTransport() | Testing -- silently accepts all emails |
Resend Transport
The primary transport for production use:
import { createResendTransport } from "@raypx/email/transports"
const transport = createResendTransport({
apiKey: "re_...",
fromEmail: "noreply@example.com",
fromName: "Raypx",
})A pre-configured resendTransport singleton is also exported when the RESEND_API_KEY environment variable is set.
Logger Transport
Logs email details to the console via @raypx/logger instead of sending:
import { createLoggerTransport } from "@raypx/email/transports"
const transport = createLoggerTransport({ feature: "auth" })
// Logs: email.send { to: "user@example.com", subject: "Verify your email" }Noop Transport
Does nothing. Useful in test environments:
import { createNoopTransport } from "@raypx/email/transports"
const transport = createNoopTransport()Templates
Built-in templates are provided for common email flows:
| Template | Function | Description |
|---|---|---|
emailVerificationTemplate | renderEmailVerificationEmail() | Email address verification on sign-up |
passwordResetTemplate | renderPasswordResetEmail() | Password reset link |
otpTemplate | -- | One-time password delivery |
Using Templates
Templates are React Email components that return { subject, html, text }:
import { renderEmailVerificationEmail } from "@raypx/email/templates"
import { sendMail, createSender } from "@raypx/email"
const { subject, html, text } = renderEmailVerificationEmail({
email: "user@example.com",
name: "John",
url: "https://example.com/verify?token=abc",
})
await sendMail(
{ from: { email: "noreply@example.com" }, to: { email: "user@example.com" }, subject, html, text },
{ transport },
)Rendering
Two rendering functions are available:
import { renderTemplate, renderTemplateAsync } from "@raypx/email"
// Synchronous or async depending on template
const result = renderTemplate(myTemplate, input)
// Always returns a Promise with resolved HTML
const result = await renderTemplateAsync(myTemplate, input)renderTemplateAsync is the safe choice -- it handles both sync and async templates and ensures the html field is fully resolved before returning.
Creating Custom Templates
Create a template function that matches the EmailTemplate<TInput> or AsyncEmailTemplate<TInput> type:
import type { EmailTemplate } from "@raypx/email"
type WelcomeInput = { name: string; email: string }
const welcomeTemplate: EmailTemplate<WelcomeInput> = ({ name, email }) => ({
subject: `Welcome to Raypx, ${name}!`,
html: `<h1>Welcome, ${name}!</h1><p>Your account has been created.</p>`,
text: `Welcome, ${name}! Your account has been created.`,
})For React Email templates with async rendering (e.g., using renderAsync):
import type { AsyncEmailTemplate } from "@raypx/email"
import { renderAsync } from "@react-email/render"
const richTemplate: AsyncEmailTemplate<{ name: string }> = async ({ name }) => ({
subject: "Welcome",
html: await renderAsync(<WelcomeEmail name={name} />),
})Configuration
Environment variables for the Resend transport:
| Variable | Required | Description |
|---|---|---|
RESEND_API_KEY | Yes (for Resend) | Resend API key, must start with re_ |
RESEND_FROM_EMAIL | Yes (for Resend) | Default sender email address |
EMAIL_FROM | No | Alternative sender email |
EMAIL_FROM_NAME | No | Sender display name |
EMAIL_TEMPLATES_BASE_URL | No | Base URL used in email template links |