Enviar Emails con Resend

La función principal de Resend es resend.emails.send(). Recibe un objeto con los datos del email y te devuelve el ID del mensaje o un error. Así de simple.

Anatomía de la función

typescript
const { data, error } = await resend.emails.send({
  // Obligatorios
  from: 'Nombre <email@tudominio.com>',
  to: ['destinatario@email.com'],
  subject: 'Asunto del email',
 
  // Contenido (al menos uno es obligatorio)
  html: '<p>Contenido HTML</p>',
  // O: react: <MiComponente />,
  // O: text: 'Contenido en texto plano',
 
  // Opcionales
  cc: ['copia@email.com'],
  bcc: ['copia-oculta@email.com'],
  replyTo: 'responder-a@email.com',
  headers: { 'X-Custom-Header': 'valor' },
  attachments: [],
  tags: [{ name: 'tipo', value: 'bienvenida' }],
  scheduledAt: 'in 1 hour',
})

Parámetros obligatorios

from (string)

La dirección del remitente. El formato recomendado incluye el nombre y el email entre corchetes angulares:

typescript
from: 'Mi App <notificaciones@midominio.com>'

El dominio del email debe estar verificado en tu cuenta de Resend. Para pruebas puedes usar @resend.dev.

El campo from debe usar un dominio verificado

Si intentas enviar desde un dominio que no has verificado en Resend, la API te devuelve un error de validación. El único dominio que funciona sin verificar es resend.dev, que Resend te proporciona para testing.

to (string | string[])

La dirección o direcciones del destinatario. Puedes pasar un string o un array:

typescript
// Un destinatario
to: 'usuario@email.com'
 
// Múltiples destinatarios
to: ['usuario1@email.com', 'usuario2@email.com', 'usuario3@email.com']

subject (string)

El asunto del email. No tiene límite de caracteres pero los clientes de correo típicamente muestran entre 40 y 60 caracteres en la vista previa.

Contenido: html, react o text

Necesitas proporcionar al menos una forma de contenido:

HTML directo:

typescript
const { data, error } = await resend.emails.send({
  from: 'Mi App <noreply@midominio.com>',
  to: ['usuario@email.com'],
  subject: 'Confirmación de cuenta',
  html: `
    <h1>Bienvenido a Mi App</h1>
    <p>Tu cuenta ha sido creada exitosamente.</p>
    <a href="https://miapp.com/verificar?token=abc123">Verificar email</a>
  `
})

Componente de React Email:

typescript
import { WelcomeEmail } from '@/emails/welcome'
 
const { data, error } = await resend.emails.send({
  from: 'Mi App <noreply@midominio.com>',
  to: ['usuario@email.com'],
  subject: 'Bienvenido',
  react: WelcomeEmail({ nombre: 'Carlos' })
})

Texto plano:

typescript
const { data, error } = await resend.emails.send({
  from: 'Mi App <noreply@midominio.com>',
  to: ['usuario@email.com'],
  subject: 'Tu código de verificación',
  text: 'Tu código de verificación es: 482910. Expira en 10 minutos.'
})
Usa tags para organizar tus analytics

El parámetro tags te permite categorizar emails en el dashboard de Resend. Por ejemplo, puedes etiquetar emails como tipo: bienvenida, tipo: factura o tipo: reset-password para filtrar métricas por categoría.

Parámetros opcionales

ParámetroTipoDescripción
ccstring | string[]Direcciones en copia (CC -- Carbon Copy)
bccstring | string[]Direcciones en copia oculta (BCC -- Blind Carbon Copy)
replyTostring | string[]Dirección de respuesta (si es diferente al from)
headersRecord<string, string>Headers HTTP personalizados
attachmentsAttachment[]Archivos adjuntos
tagsTag[]Etiquetas para analytics
scheduledAtstringProgramar envío futuro

El objeto de respuesta

Cuando el envío es exitoso, data contiene el ID del email:

typescript
// Envío exitoso
{
  data: {
    id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
  },
  error: null
}

El id es el identificador único del email en Resend. Lo usas para:

  • Consultar el estado del email via la API
  • Cancelar un email programado
  • Correlacionar con eventos de webhooks

Cuando hay un error, error contiene los detalles:

typescript
// Error
{
  data: null,
  error: {
    statusCode: 422,
    message: 'The "to" field is required.',
    name: 'validation_error'
  }
}

Manejo de errores

Los errores más comunes que vas a encontrar:

ErrorCausaSolución
validation_errorFaltan campos obligatorios o el formato es inválidoRevisa que from, to, subject y al menos un tipo de contenido estén presentes
not_foundEl dominio no está verificadoVerifica tu dominio en el dashboard de Resend
rate_limit_exceededSuperaste el límite de requests por segundoImplementa retry con backoff exponencial
unauthorizedAPI key inválida o expiradaVerifica tu API key en Settings > API Keys
internal_server_errorError interno de ResendReintenta después de unos segundos

Un patrón robusto para manejar errores:

typescript
import { resend } from '@/lib/resend'
 
async function enviarEmail(destinatario: string, asunto: string, contenido: string) {
  const { data, error } = await resend.emails.send({
    from: 'Mi App <noreply@midominio.com>',
    to: [destinatario],
    subject: asunto,
    html: contenido,
  })
 
  if (error) {
    console.error(`Error enviando email a ${destinatario}:`, error.message)
    throw new Error(`Falló el envío: ${error.name} - ${error.message}`)
  }
 
  console.log(`Email enviado exitosamente. ID: ${data.id}`)
  return data.id
}

Enviar a múltiples destinatarios

Puedes enviar el mismo email a varias personas pasando un array en to:

typescript
const { data, error } = await resend.emails.send({
  from: 'Mi App <noreply@midominio.com>',
  to: ['usuario1@email.com', 'usuario2@email.com', 'usuario3@email.com'],
  subject: 'Actualización importante',
  html: '<p>Tenemos novedades para compartir.</p>'
})

Todos los destinatarios reciben el mismo email y pueden ver las direcciones de los demás. Si quieres que cada destinatario solo vea su propia dirección, usa el envío en lote (batch) que cubrimos más adelante.