Opciones Avanzadas de Envío

Más allá de enviar un email básico, Resend te permite adjuntar archivos, programar envíos, agregar headers personalizados y usar tags para analytics. En esta sección cubrimos cada opción en detalle.

Adjuntos (attachments)

Puedes adjuntar archivos a tus emails de dos formas: como buffer (datos binarios en memoria) o como URL remota.

Desde un buffer:

typescript
import { readFileSync } from 'fs'
import { resend } from '@/lib/resend'
 
const pdfBytes = readFileSync('/ruta/a/factura.pdf')
 
const { data, error } = await resend.emails.send({
  from: 'Facturación <facturas@midominio.com>',
  to: ['cliente@email.com'],
  subject: 'Tu factura de marzo 2026',
  html: '<p>Adjuntamos tu factura del mes. Gracias por tu preferencia.</p>',
  attachments: [
    {
      filename: 'factura-marzo-2026.pdf',
      content: Buffer.from(pdfBytes),
    }
  ]
})

Desde una URL remota:

typescript
const { data, error } = await resend.emails.send({
  from: 'Mi App <noreply@midominio.com>',
  to: ['usuario@email.com'],
  subject: 'Tu reporte semanal',
  html: '<p>Aquí tienes el reporte de esta semana.</p>',
  attachments: [
    {
      filename: 'reporte.pdf',
      path: 'https://mi-bucket.s3.amazonaws.com/reportes/semanal.pdf',
    },
    {
      filename: 'logo.png',
      path: 'https://midominio.com/images/logo.png',
    }
  ]
})
Límite de 40MB en adjuntos

El tamaño total de todos los adjuntos no puede superar los 40MB. Si necesitas enviar archivos más pesados, sube el archivo a un servicio de almacenamiento (S3, Supabase Storage, etc.) e incluye un enlace de descarga en el email.

Headers personalizados

Los headers (encabezados HTTP) te permiten agregar metadatos al email. Son útiles para tracking interno, integración con otros sistemas o para cumplir con estándares de email:

typescript
const { data, error } = await resend.emails.send({
  from: 'Mi App <noreply@midominio.com>',
  to: ['usuario@email.com'],
  subject: 'Confirmación de pedido #4521',
  html: '<p>Tu pedido ha sido confirmado.</p>',
  headers: {
    'X-Entity-Ref-ID': 'pedido-4521',
    'X-Custom-Metadata': 'tipo:confirmacion',
    'List-Unsubscribe': '<https://midominio.com/desuscribir>',
  }
})

Los headers personalizados con prefijo X- son libres -- puedes nombrarlos como quieras. Los headers estándar como List-Unsubscribe tienen significado para los clientes de correo y pueden mejorar tu deliverability (capacidad de entrega).

Tags para analytics

Los tags (etiquetas) te permiten categorizar emails y filtrar métricas en el dashboard de Resend:

typescript
const { data, error } = await resend.emails.send({
  from: 'Mi App <noreply@midominio.com>',
  to: ['usuario@email.com'],
  subject: 'Bienvenido a Mi App',
  html: '<p>Gracias por registrarte.</p>',
  tags: [
    { name: 'tipo', value: 'bienvenida' },
    { name: 'plan', value: 'free' },
    { name: 'fuente', value: 'registro-web' },
  ]
})

Después puedes ir al dashboard y filtrar por tipo: bienvenida para ver cuántos emails de bienvenida se enviaron, cuántos se abrieron, etc.

Reglas de los tags:

  • Máximo 5 tags por email
  • El name acepta hasta 256 caracteres
  • El value acepta hasta 256 caracteres
  • Solo caracteres alfanuméricos, guiones y guiones bajos

Programar envío (scheduledAt)

Puedes programar un email para que se envíe en el futuro con el parámetro scheduledAt. Acepta dos formatos:

Formato relativo:

typescript
const { data, error } = await resend.emails.send({
  from: 'Mi App <noreply@midominio.com>',
  to: ['usuario@email.com'],
  subject: 'Recordatorio de tu cita',
  html: '<p>Tu cita es mañana a las 10:00 AM.</p>',
  scheduledAt: 'in 1 hour',
})

Formato ISO 8601:

typescript
const { data, error } = await resend.emails.send({
  from: 'Mi App <noreply@midominio.com>',
  to: ['usuario@email.com'],
  subject: 'Resumen semanal',
  html: '<p>Tu resumen de la semana.</p>',
  scheduledAt: '2026-03-15T09:00:00Z',
})

El email queda en estado "scheduled" hasta que llegue la hora programada.

Cancelar un email programado

Si programaste un email y necesitas cancelarlo antes de que se envíe:

typescript
const { data: cancelData, error } = await resend.emails.cancel('a1b2c3d4-e5f6-7890-abcd-ef1234567890')

Solo puedes cancelar emails que aún estén en estado "scheduled". Una vez que Resend inicia el envío, ya no es posible cancelarlo.

Reply-To

Si quieres que las respuestas al email lleguen a una dirección diferente al from:

typescript
const { data, error } = await resend.emails.send({
  from: 'Mi App <noreply@midominio.com>',
  to: ['usuario@email.com'],
  subject: 'Soporte: Tu ticket #892',
  html: '<p>Hemos recibido tu solicitud.</p>',
  replyTo: 'soporte@midominio.com',
})

Cuando el usuario haga clic en "Responder" en su cliente de correo, la respuesta se dirige a soporte@midominio.com en vez de noreply@midominio.com.

También puedes pasar un array si quieres que las respuestas lleguen a varias direcciones:

typescript
replyTo: ['soporte@midominio.com', 'backup-soporte@midominio.com']

CC y BCC

CC (Carbon Copy -- copia) y BCC (Blind Carbon Copy -- copia oculta) funcionan como en cualquier cliente de correo:

typescript
const { data, error } = await resend.emails.send({
  from: 'Administración <admin@midominio.com>',
  to: ['empleado@midominio.com'],
  cc: ['jefe@midominio.com', 'rrhh@midominio.com'],
  bcc: ['archivo@midominio.com'],
  subject: 'Actualización de contrato',
  html: '<p>Se ha actualizado tu contrato laboral.</p>',
})
  • CC: los destinatarios en copia son visibles para todos
  • BCC: los destinatarios en copia oculta no son visibles para nadie más

Idempotency keys

Cuando tu servidor hace retry (reintento) de un request fallido, existe el riesgo de enviar el mismo email dos veces. Las idempotency keys (claves de idempotencia) evitan esto:

typescript
const { data, error } = await resend.emails.send(
  {
    from: 'Mi App <noreply@midominio.com>',
    to: ['usuario@email.com'],
    subject: 'Confirmación de pago',
    html: '<p>Tu pago ha sido procesado.</p>',
  },
  {
    headers: {
      'Idempotency-Key': 'pago-12345-confirmacion',
    }
  }
)

Si envías dos requests con la misma Idempotency-Key, Resend solo procesa el primero y te devuelve el mismo resultado para el segundo. La key es válida durante 24 horas.

Usa IDs de tu sistema como idempotency key

Un buen patrón es combinar el ID del recurso con el tipo de email: pago-12345-confirmacion, usuario-678-bienvenida, pedido-901-enviado. Así garantizas que cada email se envía exactamente una vez por recurso.

Ejemplo completo con todas las opciones

typescript
import { resend } from '@/lib/resend'
import { readFileSync } from 'fs'
 
async function enviarFactura(clienteEmail: string, facturaId: string) {
  const pdfBytes = readFileSync(`/tmp/facturas/${facturaId}.pdf`)
 
  const { data, error } = await resend.emails.send(
    {
      from: 'Facturación <facturas@midominio.com>',
      to: [clienteEmail],
      cc: ['contabilidad@midominio.com'],
      replyTo: 'soporte-facturacion@midominio.com',
      subject: `Factura ${facturaId} - Marzo 2026`,
      html: `<p>Adjuntamos tu factura. Si tienes dudas, responde a este email.</p>`,
      attachments: [
        {
          filename: `factura-${facturaId}.pdf`,
          content: Buffer.from(pdfBytes),
        }
      ],
      tags: [
        { name: 'tipo', value: 'factura' },
        { name: 'mes', value: '2026-03' },
      ],
      headers: {
        'X-Factura-ID': facturaId,
      },
    },
    {
      headers: {
        'Idempotency-Key': `factura-${facturaId}-envio`,
      }
    }
  )
 
  if (error) {
    throw new Error(`Error enviando factura ${facturaId}: ${error.message}`)
  }
 
  return data.id
}