
Introduccion
El envio de SMS masivos sigue siendo una de las formas mas efectivas de comunicacion directa con clientes. Con tasas de apertura superiores al 98%, los SMS superan ampliamente a otros canales de comunicacion.
En este tutorial aprenderemos a implementar un sistema de envio de SMS masivos utilizando APIs modernas.
Por Que SMS Masivos?
Ventajas del Canal SMS
- 98% tasa de apertura (vs 20% email)
- 90% leidos en 3 minutos
- No requiere internet ni app
- Funciona en cualquier telefono
- Alcance universal
Casos de Uso
- Marketing: Promociones, ofertas flash
- Transaccional: Confirmaciones, alertas
- Notificaciones: Recordatorios, actualizaciones
- Autenticacion: Codigos OTP, verificacion
Requisitos Previos
- Cuenta en una plataforma de SMS API
- Conocimientos basicos de programacion
- Base de datos con numeros de telefono (con consentimiento)
Paso 1: Configuracion Inicial
npm init -y
npm install @zavudev/sdk dotenv csv-parser
// config.js
require('dotenv').config();
module.exports = {
apiKey: process.env.ZAVUDEV_API_KEY,
defaultSenderId: process.env.SENDER_ID
};
Paso 2: Preparar la Lista de Contactos
// src/contacts.js
const fs = require('fs');
const csv = require('csv-parser');
const { parsePhoneNumber, isValidPhoneNumber } = require('libphonenumber-js');
async function cargarContactosCSV(archivo) {
return new Promise((resolve, reject) => {
const contactos = [];
fs.createReadStream(archivo)
.pipe(csv())
.on('data', (row) => {
const telefono = normalizarTelefono(row.telefono);
if (telefono) {
contactos.push({
telefono,
nombre: row.nombre,
// Campos adicionales para personalizacion
...row
});
}
})
.on('end', () => resolve(contactos))
.on('error', reject);
});
}
function normalizarTelefono(telefono, pais = 'ES') {
try {
const parsed = parsePhoneNumber(telefono, pais);
if (parsed.isValid()) {
return parsed.format('E.164'); // +34612345678
}
} catch (e) {
// Telefono invalido
}
return null;
}
module.exports = { cargarContactosCSV, normalizarTelefono };
Paso 3: Sistema de Envio Masivo
// src/broadcast.js
const Zavu = require('@zavudev/sdk').default;
const config = require('./config');
const zavu = new Zavudev({
apiKey: config.apiKey
});
class SMSBroadcast {
constructor(opciones = {}) {
this.rateLimit = opciones.rateLimit || 50; // Mensajes por segundo
this.reintentos = opciones.reintentos || 3;
this.resultados = {
exitosos: 0,
fallidos: 0,
errores: []
};
}
async enviar(contactos, mensaje, opciones = {}) {
console.log(`Iniciando envio a ${contactos.length} contactos...`);
const lotes = this.dividirEnLotes(contactos, this.rateLimit);
for (let i = 0; i < lotes.length; i++) {
const lote = lotes[i];
console.log(`Procesando lote ${i + 1}/${lotes.length}`);
await this.procesarLote(lote, mensaje, opciones);
// Pausa entre lotes para respetar rate limits
if (i < lotes.length - 1) {
await this.esperar(1000);
}
}
return this.resultados;
}
async procesarLote(contactos, plantilla, opciones) {
const promesas = contactos.map(contacto =>
this.enviarMensaje(contacto, plantilla, opciones)
);
await Promise.all(promesas);
}
async enviarMensaje(contacto, plantilla, opciones, intento = 1) {
try {
const mensaje = this.personalizarMensaje(plantilla, contacto);
const resultado = await zavu.messages.send({
to: contacto.telefono,
text: mensaje,
...opciones
});
this.resultados.exitosos++;
return {
exito: true,
contacto,
messageId: resultado.id
};
} catch (error) {
if (intento < this.reintentos && this.esErrorReintentable(error)) {
await this.esperar(Math.pow(2, intento) * 1000);
return this.enviarMensaje(contacto, plantilla, opciones, intento + 1);
}
this.resultados.fallidos++;
this.resultados.errores.push({
contacto,
error: error.message
});
return {
exito: false,
contacto,
error: error.message
};
}
}
personalizarMensaje(plantilla, contacto) {
return plantilla.replace(/\{\{(\w+)\}\}/g, (match, campo) => {
return contacto[campo] || match;
});
}
esErrorReintentable(error) {
const codigosReintentables = ['rate_limited', 'timeout', 'server_error'];
return codigosReintentables.includes(error.code);
}
dividirEnLotes(array, tamanoLote) {
const lotes = [];
for (let i = 0; i < array.length; i += tamanoLote) {
lotes.push(array.slice(i, i + tamanoLote));
}
return lotes;
}
esperar(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
module.exports = SMSBroadcast;
Paso 4: Usando la API de Broadcasts
Las APIs de SMS modernas ofrecen endpoints especificos para broadcasts que simplifican el proceso:
// src/broadcast-api.js
const Zavu = require('@zavudev/sdk').default;
const zavu = new Zavudev({
apiKey: process.env.ZAVUDEV_API_KEY
});
async function crearCampana() {
// Crear broadcast
const broadcast = await zavu.broadcasts.create({
name: 'Promocion Black Friday',
channel: 'sms',
text: 'Hola {{nombre}}! Solo hoy: 50% OFF en toda la tienda. Usa codigo FRIDAY50. Valido hasta medianoche.'
});
console.log('Broadcast creado:', broadcast.id);
// Agregar contactos
const contactos = await cargarContactosCSV('clientes.csv');
const resultado = await zavu.broadcasts.addContacts(broadcast.id, {
contacts: contactos.map(c => ({
recipient: c.telefono,
templateVariables: { nombre: c.nombre }
}))
});
console.log(`Agregados: ${resultado.added}, Duplicados: ${resultado.duplicates}`);
// Enviar
await zavu.broadcasts.send(broadcast.id);
console.log('Campana iniciada!');
return broadcast.id;
}
async function monitorearProgreso(broadcastId) {
const progreso = await zavu.broadcasts.getProgress(broadcastId);
console.log(`
Estado: ${progreso.status}
Total: ${progreso.total}
Entregados: ${progreso.delivered}
Fallidos: ${progreso.failed}
Progreso: ${progreso.percentComplete}%
`);
return progreso;
}
Paso 5: Script Principal
// index.js
const SMSBroadcast = require('./src/broadcast');
const { cargarContactosCSV } = require('./src/contacts');
async function main() {
// Cargar contactos
const contactos = await cargarContactosCSV('contactos.csv');
console.log(`Cargados ${contactos.length} contactos validos`);
// Crear instancia del broadcast
const broadcast = new SMSBroadcast({
rateLimit: 50,
reintentos: 3
});
// Mensaje con personalizacion
const mensaje = 'Hola {{nombre}}, aprovecha nuestra oferta especial del 30% de descuento. Valido solo hoy!';
// Enviar
const resultados = await broadcast.enviar(contactos, mensaje);
console.log(`
=== RESUMEN ===
Exitosos: ${resultados.exitosos}
Fallidos: ${resultados.fallidos}
`);
if (resultados.errores.length > 0) {
console.log('Errores:', resultados.errores);
}
}
main().catch(console.error);
Mejores Practicas
1. Validar Consentimiento
async function verificarConsentimiento(telefono) {
const contacto = await database.contactos.findOne({ telefono });
return contacto?.consentimiento?.sms === true
&& contacto?.consentimiento?.fecha > hace30Dias();
}
2. Ofrecer Opcion de Baja
const mensajeConBaja = `${mensaje}
Responde BAJA para cancelar.`;
// Procesar respuestas de baja
async function procesarRespuesta(from, texto) {
if (texto.toLowerCase() === 'baja') {
await database.contactos.update(
{ telefono: from },
{ $set: { 'consentimiento.sms': false } }
);
await enviarConfirmacionBaja(from);
}
}
3. Programar Envios
const cron = require('node-cron');
// Enviar promociones los viernes a las 10:00
cron.schedule('0 10 * * 5', async () => {
const contactos = await obtenerContactosActivos();
await enviarPromocionSemanal(contactos);
});
4. Monitorear Resultados
async function generarReporte(broadcastId) {
const broadcast = await zavu.broadcasts.get(broadcastId);
return {
nombre: broadcast.name,
total: broadcast.totalContacts,
entregados: broadcast.deliveredCount,
fallidos: broadcast.failedCount,
tasaEntrega: (broadcast.deliveredCount / broadcast.totalContacts * 100).toFixed(2) + '%',
costo: broadcast.actualCost
};
}
Cumplimiento Legal
Espana (LSSI)
- Consentimiento previo obligatorio
- Identificar al remitente
- Ofrecer metodo de baja facil
Latinoamerica
Cada pais tiene regulaciones especificas. Consulta la normativa local antes de enviar.
Optimizacion de Costos
Estrategias
- Segmentar audiencia: Solo enviar a contactos relevantes
- Limpiar lista: Eliminar numeros invalidos
- Horarios optimos: Enviar cuando hay mayor engagement
- Mensajes concisos: Menos caracteres = menos costo
function optimizarMensaje(texto) {
// Maximo 160 caracteres para un solo SMS
if (texto.length > 160) {
console.warn('Mensaje excede 160 caracteres. Se enviara como SMS concatenado.');
}
// Evitar caracteres especiales que aumentan longitud
return texto
.replace(/[""]/g, '"')
.replace(/['']/g, "'");
}
Conclusion
El envio de SMS masivos con API permite comunicarse de forma efectiva con miles de clientes de manera programatica y escalable. Siguiendo las mejores practicas de validacion, personalizacion y cumplimiento legal, puedes maximizar el impacto de tus campanas.
Las plataformas de SMS API modernas simplifican enormemente este proceso, ofreciendo funcionalidades como broadcasts gestionados, personalizacion automatica y analytics en tiempo real.
Recursos
- Guia de Cumplimiento LSSI
- Mejores Practicas de Marketing por SMS
- API de SMS para Desarrolladores



Aún no hay comentarios.