Envío de Emails en Lote (Batch)
Cuando necesitas enviar emails a múltiples destinatarios con contenido personalizado para cada uno, la función resend.batch.send() te permite enviar hasta 100 emails en una sola petición HTTP. Cada email puede tener diferente destinatario, asunto, contenido y configuración.
La función batch.send()
import { resend } from '@/lib/resend'
const { data, error } = await resend.batch.send([
{
from: 'Mi App <noreply@midominio.com>',
to: ['usuario1@email.com'],
subject: 'Hola Carlos',
html: '<p>Tu reporte personalizado está listo.</p>',
},
{
from: 'Mi App <noreply@midominio.com>',
to: ['usuario2@email.com'],
subject: 'Hola María',
html: '<p>Tu reporte personalizado está listo.</p>',
},
{
from: 'Mi App <noreply@midominio.com>',
to: ['usuario3@email.com'],
subject: 'Hola Pedro',
html: '<p>Tu reporte personalizado está listo.</p>',
},
])La función recibe un array de objetos, donde cada objeto tiene la misma estructura que resend.emails.send(). Todos los parámetros que ya conoces (from, to, subject, html, react, text, attachments, tags, etc.) están disponibles para cada email individual.
El objeto de respuesta
// Respuesta exitosa
{
data: {
data: [
{ id: 'email-id-1' },
{ id: 'email-id-2' },
{ id: 'email-id-3' },
]
},
error: null
}Resend te devuelve un array de objetos { id }, uno por cada email en el batch. El orden corresponde al orden en que pasaste los emails en el array.
Cuándo usar batch
El envío en lote es útil cuando:
- Notificaciones a un equipo: un usuario crea una tarea y todos los miembros del equipo reciben una notificación personalizada
- Emails de onboarding: varios usuarios se registraron y quieres enviarles el email de bienvenida con su nombre
- Alertas del sistema: algo pasó en producción y necesitas notificar a todos los administradores
- Reportes personalizados: cada usuario recibe un resumen con sus propios datos
Cada email puede ser diferente
A diferencia de enviar un email con múltiples destinatarios en to (donde todos reciben exactamente lo mismo), en batch cada email es independiente:
const miembros = [
{ email: 'carlos@empresa.com', nombre: 'Carlos', rol: 'admin' },
{ email: 'maria@empresa.com', nombre: 'María', rol: 'developer' },
{ email: 'pedro@empresa.com', nombre: 'Pedro', rol: 'designer' },
]
const emails = miembros.map((miembro) => ({
from: 'Mi App <noreply@midominio.com>',
to: [miembro.email],
subject: `${miembro.nombre}, tienes una nueva tarea asignada`,
html: `
<p>Hola ${miembro.nombre},</p>
<p>Se te ha asignado una nueva tarea en el proyecto.</p>
<p>Tu rol actual: ${miembro.rol}</p>
<a href="https://miapp.com/tareas">Ver mis tareas</a>
`,
tags: [
{ name: 'tipo', value: 'notificacion-tarea' },
{ name: 'rol', value: miembro.rol },
],
}))
const { data, error } = await resend.batch.send(emails)Cada destinatario recibe un email personalizado y solo ve su propia dirección. No hay CC ni BCC implícitos.
Límites y rate limiting
| Restricción | Valor |
|---|---|
| Emails por petición batch | Máximo 100 |
| Rate limit | 2 requests por segundo por equipo (team) |
| Tamaño total de la petición | Los mismos límites que emails individuales |
Si necesitas enviar más de 100 emails, divide tu lista en chunks (bloques) de 100:
import { resend } from '@/lib/resend'
interface EmailData {
email: string
nombre: string
}
async function enviarNotificacionMasiva(usuarios: EmailData[]) {
const BATCH_SIZE = 100
const resultados: string[] = []
for (let i = 0; i < usuarios.length; i += BATCH_SIZE) {
const chunk = usuarios.slice(i, i + BATCH_SIZE)
const emails = chunk.map((usuario) => ({
from: 'Mi App <noreply@midominio.com>' as const,
to: [usuario.email],
subject: `${usuario.nombre}, actualización importante`,
html: `<p>Hola ${usuario.nombre}, tenemos novedades.</p>`,
}))
const { data, error } = await resend.batch.send(emails)
if (error) {
console.error(`Error en batch ${i / BATCH_SIZE + 1}:`, error.message)
continue
}
const ids = data.data.map((item) => item.id)
resultados.push(...ids)
// Respetar el rate limit de 2 requests/segundo
if (i + BATCH_SIZE < usuarios.length) {
await new Promise((resolve) => setTimeout(resolve, 500))
}
}
return resultados
}Respeta el rate limit
El rate limit de Resend para batch es de 2 requests por segundo por equipo. Si envías requests más rápido, recibirás un error rate_limit_exceeded. Implementa una pausa de al menos 500ms entre peticiones batch consecutivas.
Batch vs Broadcast: cuándo usar cada uno
Batch (resend.batch.send) | Broadcast (Audiencias) | |
|---|---|---|
| Control | Programático, desde tu código | Dashboard o API de Audiencias |
| Personalización | Total, cada email es diferente | Variables en templates |
| Límite | 100 por petición | Miles de contactos |
| Caso de uso | Notificaciones, alertas, emails transaccionales a grupos | Newsletters, campañas de marketing |
| Gestión de contactos | Tú la manejas | Resend la maneja (suscripciones, desuscripciones) |
Para miles de contactos usa Broadcasts con Audiencias
Si necesitas enviar un newsletter o una campaña a miles de contactos, no uses batch. Usa la funcionalidad de Broadcasts con Audiencias de Resend. Broadcasts gestiona automáticamente las suscripciones, desuscripciones y el cumplimiento de regulaciones como CAN-SPAM. Batch está diseñado para envíos programáticos donde tú controlas la lista de destinatarios desde tu código.
Ejemplo real: notificar a todos los miembros de un equipo
Un caso de uso concreto: un usuario sube un archivo al proyecto y quieres notificar a todo el equipo.
import { resend } from '@/lib/resend'
interface TeamMember {
email: string
nombre: string
}
interface ArchivoSubido {
nombre: string
tamano: string
url: string
subidoPor: string
}
async function notificarEquipo(equipo: TeamMember[], archivo: ArchivoSubido) {
const emails = equipo.map((miembro) => ({
from: 'Proyectos <proyectos@midominio.com>' as const,
to: [miembro.email],
subject: `${archivo.subidoPor} subió un archivo al proyecto`,
html: `
<p>Hola ${miembro.nombre},</p>
<p><strong>${archivo.subidoPor}</strong> acaba de subir un nuevo archivo:</p>
<ul>
<li>Archivo: ${archivo.nombre}</li>
<li>Tamaño: ${archivo.tamano}</li>
</ul>
<p><a href="${archivo.url}">Descargar archivo</a></p>
`,
tags: [
{ name: 'tipo', value: 'notificacion-archivo' },
],
}))
const { data, error } = await resend.batch.send(emails)
if (error) {
console.error('Error notificando al equipo:', error.message)
throw new Error(`Falló la notificación: ${error.message}`)
}
console.log(`${equipo.length} miembros notificados exitosamente`)
return data.data.map((item) => item.id)
}Manejo de errores en batch
Si alguno de los emails en el batch tiene un error de validación, todo el batch falla. No hay envío parcial. Asegúrate de validar los datos antes de enviar:
function validarEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
const emailsValidos = usuarios
.filter((u) => validarEmail(u.email))
.map((u) => ({
from: 'Mi App <noreply@midominio.com>' as const,
to: [u.email],
subject: 'Notificación',
html: `<p>Hola ${u.nombre}</p>`,
}))
if (emailsValidos.length > 0) {
const { data, error } = await resend.batch.send(emailsValidos)
}