EmpiezaTuSaaS
Funcionalidades

Emails

Emails transaccionales con Resend y React Email

Emails

El boilerplate usa Resend + React Email para emails transaccionales. Templates en JSX, alta deliverability y 3,000 emails/mes gratis.

Setup de Resend

  1. Crea una cuenta en resend.com
  2. Ve a API Keys > Create API Key y copia la key
  3. Agrega la variable de entorno:
.env.local
RESEND_API_KEY="re_..."
  1. (Recomendado) Verifica tu dominio en Domains del dashboard. Configura los registros DNS (MX, SPF, DKIM) y espera verificación.

Sin dominio verificado, solo puedes enviar a tu email registrado usando onboarding@resend.dev.

Las dependencias ya vienen incluidas en el boilerplate:

npm install resend @react-email/components

Configuración

Cliente Resend con lazy initialization

El boilerplate usa un patron lazy Proxy para evitar errores de build cuando RESEND_API_KEY no está configurada (CI, primer deploy):

lib/email.ts
import { Resend } from "resend";
import { siteConfig } from "@/config/site";

let _resend: Resend | null = null;

function getResend(): Resend {
  if (!process.env.RESEND_API_KEY) {
    throw new Error("RESEND_API_KEY is not set");
  }
  if (!_resend) {
    _resend = new Resend(process.env.RESEND_API_KEY);
  }
  return _resend;
}

export const resend = new Proxy({} as Resend, {
  get(_target, prop) {
    return getResend()[prop as keyof Resend];
  },
});

const FROM_EMAIL =
  `${siteConfig.name} <${siteConfig.noReplyEmail}>`;

El cliente no se instancia hasta que se use por primera vez. Esto permite que next build funcione sin RESEND_API_KEY en el entorno.

Remitente y email de soporte

El remitente se construye desde siteConfig. Cambia estos valores por los de tu dominio verificado:

config/site.ts
export const siteConfig = {
  name: "EmpiezaTuSaaS",
  noReplyEmail: "noreply@empiezatusaas.com",
  supportEmail: "soporte@empiezatusaas.com",
  // ...
} as const;

Estructura del directorio

emails/
├── welcome.tsx          # Email de bienvenida
├── verify-email.tsx     # Verificación de email
└── reset-password.tsx   # Restablecimiento de contraseña

Templates incluidos

Todos los templates usan componentes de @react-email/components y leen la configuración desde siteConfig.

TemplateArchivoComponenteDisparado por
Verificaciónemails/verify-email.tsxVerifyEmailTemplateBetter Auth (registro)
Contraseñaemails/reset-password.tsxResetPasswordTemplateBetter Auth (olvido de contraseña)
Bienvenidaemails/welcome.tsxWelcomeTemplateManualmente despues del registro

VerifyEmailTemplate

Verifica la direccion de email del usuario. Se envia automáticamente por Better Auth.

emails/verify-email.tsx
import {
  Body, Button, Container, Head, Heading,
  Hr, Html, Preview, Text,
} from "@react-email/components";
import { siteConfig } from "@/config/site";

interface VerifyEmailTemplateProps {
  name: string;
  verificationUrl: string;
}

export function VerifyEmailTemplate({
  name, verificationUrl,
}: VerifyEmailTemplateProps) {
  return (
    <Html>
      <Head />
      <Preview>Verifica tu email para acceder a {siteConfig.name}</Preview>
      <Body style={main}>
        <Container style={container}>
          <Heading style={h1}>Verifica tu email</Heading>
          <Text style={text}>Hola {name},</Text>
          <Text style={text}>
            Gracias por registrarte en {siteConfig.name}. Por favor, verifica tu
            direccion de email haciendo clic en el boton de abajo.
          </Text>
          <Button style={button} href={verificationUrl}>
            Verificar Email
          </Button>
          <Text style={text}>
            Este enlace expira en 24 horas. Si no creaste está cuenta, puedes
            ignorar este email.
          </Text>
          <Hr style={hr} />
          <Text style={footer}>
            {siteConfig.name} — {siteConfig.domain}
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

ResetPasswordTemplate

Se envia cuando el usuario solicita restablecer su contraseña.

emails/reset-password.tsx
import {
  Body, Button, Container, Head, Heading,
  Hr, Html, Preview, Text,
} from "@react-email/components";
import { siteConfig } from "@/config/site";

interface ResetPasswordTemplateProps {
  name: string;
  resetUrl: string;
}

export function ResetPasswordTemplate({
  name, resetUrl,
}: ResetPasswordTemplateProps) {
  return (
    <Html>
      <Head />
      <Preview>Restablece tu contraseña de {siteConfig.name}</Preview>
      <Body style={main}>
        <Container style={container}>
          <Heading style={h1}>Restablece tu contraseña</Heading>
          <Text style={text}>Hola {name},</Text>
          <Text style={text}>
            Recibimos una solicitud para restablecer la contraseña de tu cuenta
            en {siteConfig.name}. Haz clic en el boton de abajo para crear una
            nueva contraseña.
          </Text>
          <Button style={button} href={resetUrl}>
            Restablecer Contraseña
          </Button>
          <Text style={text}>
            Este enlace expira en 1 hora. Si no solicitaste este cambio, puedes
            ignorar este email de forma segura.
          </Text>
          <Hr style={hr} />
          <Text style={footer}>
            {siteConfig.name} — {siteConfig.domain}
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

WelcomeTemplate

Se envia al registrarse el usuario. Muestra un saludo personalizado y un boton al dashboard.

emails/welcome.tsx
import {
  Body, Button, Container, Head, Heading,
  Hr, Html, Preview, Section, Text,
} from "@react-email/components";
import { siteConfig } from "@/config/site";

interface WelcomeTemplateProps {
  name: string;
}

export function WelcomeTemplate({ name }: WelcomeTemplateProps) {
  return (
    <Html>
      <Head />
      <Preview>Bienvenido a {siteConfig.name}! Tu SaaS comienza aqui.</Preview>
      <Body style={main}>
        <Container style={container}>
          <Heading style={h1}>Bienvenido, {name}!</Heading>
          <Text style={text}>
            Gracias por unirte a {siteConfig.name}. Estas a un paso de lanzar
            tu SaaS mas rapido que nunca.
          </Text>
          <Section style={section}>
            <Text style={text}>Con tu acceso puedes:</Text>
            <Text style={listItem}>Clonar el boilerplate y empezar en minutos</Text>
            <Text style={listItem}>Usar auth, pagos y emails ya configurados</Text>
            <Text style={listItem}>Desplegar en Vercel con un clic</Text>
          </Section>
          <Button style={button} href={siteConfig.url + "/dashboard"}>
            Ir al Dashboard
          </Button>
          <Hr style={hr} />
          <Text style={footer}>
            Tienes dudas? Respondenos a {siteConfig.supportEmail}
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

Estilos compartidos

Los templates comparten estilos inline (los clientes de email no soportan <style> de forma consistente):

const main = {
  backgroundColor: "#f6f9fc",
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
};

const container = {
  backgroundColor: "#ffffff",
  margin: "0 auto",
  padding: "20px 0 48px",
  maxWidth: "580px",
};

const button = {
  backgroundColor: "#18181b",
  borderRadius: "8px",
  color: "#fff",
  fontSize: "16px",
  fontWeight: "600",
  textDecoration: "none",
  textAlign: "center" as const,
  display: "block",
  padding: "12px 24px",
  margin: "24px 48px",
};

Enviar emails

Todas las funciones siguen el mismo patron: reciben un objeto con los parametros, importan el template dinámicamente y envian via Resend.

sendVerificationEmail

lib/email.ts
export async function sendVerificationEmail({
  to, name, verificationUrl,
}: { to: string; name: string; verificationUrl: string }) {
  const { VerifyEmailTemplate } = await import("@/emails/verify-email");
  const { createElement } = await import("react");

  return getResend().emails.send({
    from: FROM_EMAIL,
    to,
    subject: `Verifica tu email - ${siteConfig.name}`,
    react: createElement(VerifyEmailTemplate, { name, verificationUrl }),
  });
}

sendPasswordResetEmail

lib/email.ts
export async function sendPasswordResetEmail({
  to, name, resetUrl,
}: { to: string; name: string; resetUrl: string }) {
  const { ResetPasswordTemplate } = await import("@/emails/reset-password");
  const { createElement } = await import("react");

  return getResend().emails.send({
    from: FROM_EMAIL,
    to,
    subject: `Restablece tu contraseña - ${siteConfig.name}`,
    react: createElement(ResetPasswordTemplate, { name, resetUrl }),
  });
}

sendWelcomeEmail

lib/email.ts
export async function sendWelcomeEmail({
  to, name,
}: { to: string; name: string }) {
  const { WelcomeTemplate } = await import("@/emails/welcome");
  const { createElement } = await import("react");

  return getResend().emails.send({
    from: FROM_EMAIL,
    to,
    subject: `Bienvenido a ${siteConfig.name}!`,
    react: createElement(WelcomeTemplate, { name }),
  });
}

Integracion con Better Auth

Las funciones se conectan con Better Auth en lib/auth.ts:

lib/auth.ts
import { betterAuth } from "better-auth";
import { sendVerificationEmail, sendPasswordResetEmail } from "@/lib/email";

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    sendResetPassword: async ({ user, url }) => {
      await sendPasswordResetEmail({
        to: user.email,
        name: user.name,
        resetUrl: url,
      });
    },
  },
  emailVerification: {
    sendVerificationEmail: async ({ user, url }) => {
      await sendVerificationEmail({
        to: user.email,
        name: user.name,
        verificationUrl: url,
      });
    },
    sendOnSignUp: true,
  },
});

Agregar un nuevo email

  1. Crea el template en emails/tu-template.tsx usando @react-email/components
  2. Crea la funcion de envio en lib/email.ts:
lib/email.ts
export async function sendCustomEmail({
  to, /* tus props */
}: { to: string }) {
  const { TuTemplate } = await import("@/emails/tu-template");
  const { createElement } = await import("react");

  return getResend().emails.send({
    from: FROM_EMAIL,
    to,
    subject: `Tu asunto - ${siteConfig.name}`,
    react: createElement(TuTemplate, { /* props */ }),
  });
}
  1. Invocala desde tu API route o server action.

Preview en desarrollo

Para previsualizar emails durante el desarrollo:

npx email dev --dir emails

Abre http://localhost:3000 para ver tus templates renderizados.

Recibir emails

El boilerplate configura supportEmail en siteConfig como direccion de contacto. Los templates de email incluyen este valor para que los usuarios puedan responder con dudas:

config/site.ts
supportEmail: "soporte@empiezatusaas.com",

Los templates usan siteConfig.supportEmail en el footer para que los usuarios sepan donde escribir. Si necesitas recibir emails programaticamente, Resend no soporta inbound emails - usa un servicio como Mailgun o configura un formulario de contacto.

Resend tiene un limite de 3,000 emails/mes en el plan gratuito. Para mas volumen, consulta sus planes de pago.

On this page