emailr_
Todos los artículos
usecase·11 min

Email de alto volumen: escalando a millones al día

scaleinfrastructurearchitecture

Enviar millones de emails al día requiere una arquitectura cuidadosa. La gestión de colas, la limitación de tasa y el diseño de la infraestructura afectan a la entregabilidad y la fiabilidad. Aquí te mostramos cómo diseñar para escalar.

Resumen de la arquitectura

Componentes principales

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   API/App   │────▶│    Queue    │────▶│   Workers   │
└─────────────┘     └─────────────┘     └─────────────┘
                                               │
                    ┌─────────────┐            │
                    │   Provider  │◀───────────┘
                    │     API     │
                    └─────────────┘

Diseño de colas

interface EmailJob {
  id: string;
  priority: 'critical' | 'high' | 'normal' | 'low';
  payload: {
    to: string;
    from: string;
    subject: string;
    html: string;
  };
  metadata: {
    userId: string;
    campaignId?: string;
    scheduledFor?: Date;
  };
  attempts: number;
  maxAttempts: number;
  createdAt: Date;
}


// Priority queues
const queues = {
  critical: 'email:critical',  // Password resets, 2FA
  high: 'email:high',          // Transactional
  normal: 'email:normal',      // Notifications
  low: 'email:low'             // Marketing, digests
};

Limitación de tasa

Límites del proveedor

class RateLimiter {
  private limits: Map<string, { count: number; resetAt: Date }> = new Map();
  
  async canSend(provider: string): Promise<boolean> {
    const limit = this.limits.get(provider);
    
    if (!limit || limit.resetAt < new Date()) {
      this.limits.set(provider, {
        count: 1,
        resetAt: addSeconds(new Date(), 1)
      });
      return true;
    }
    
    if (limit.count >= PROVIDER_RATE_LIMITS[provider]) {
      return false;
    }
    
    limit.count++;
    return true;
  }
}

Throttling de ISP

const ispLimits = {
  'gmail.com': { perHour: 500, perDay: 5000 },
  'yahoo.com': { perHour: 200, perDay: 2000 },
  'outlook.com': { perHour: 300, perDay: 3000 }
};

async function getThrottledBatch(emails: Email[]): Promise<Email[]> {
  const byDomain = groupBy(emails, e => getDomain(e.to));
  const batch: Email[] = [];
  
  for (const [domain, domainEmails] of Object.entries(byDomain)) {
    const limit = ispLimits[domain] || { perHour: 1000 };
    const sent = await getSentCount(domain, 'hour');
    const available = limit.perHour - sent;
    
    batch.push(...domainEmails.slice(0, available));
  }
  
  return batch;
}

Escalado de workers

Escalado horizontal

// Worker configuration
const workerConfig = {
  concurrency: 50,           // Emails per worker
  batchSize: 100,            // Fetch from queue
  pollInterval: 100,         // ms between polls
  maxRetries: 3,
  retryDelay: [1000, 5000, 30000] // Exponential backoff
};

async function processQueue(queue: string) {
  while (true) {
    const jobs = await redis.lpop(queue, workerConfig.batchSize);
    
    if (jobs.length === 0) {
      await sleep(workerConfig.pollInterval);
      continue;
    }
    
    await Promise.all(
      jobs.map(job => processJob(JSON.parse(job)))
    );
  }
}

Disparadores de autoescalado

const scalingRules = {
  scaleUp: {
    queueDepth: 10000,      // Jobs waiting
    processingTime: 5000,   // ms per job
    errorRate: 0.05         // 5% errors
  },
  scaleDown: {
    queueDepth: 100,
    idleTime: 300000        // 5 minutes idle
  }
};

Procesamiento por lotes

Procesamiento por lotes eficiente

async function sendBatch(emails: Email[]): Promise<BatchResult> {
  // Group by template for efficiency
  const byTemplate = groupBy(emails, 'templateId');
  const results: SendResult[] = [];
  
  for (const [templateId, templateEmails] of Object.entries(byTemplate)) {
    // Compile template once
    const template = await getCompiledTemplate(templateId);
    
    // Send in parallel with concurrency limit
    const batchResults = await pMap(
      templateEmails,
      email => sendWithTemplate(email, template),
      { concurrency: 50 }
    );
    
    results.push(...batchResults);
  }
  
  return { sent: results.filter(r => r.success).length, results };
}

Monitorización a escala

Métricas clave

const metrics = {
  // Throughput
  emailsPerSecond: gauge('emails_per_second'),
  emailsPerMinute: gauge('emails_per_minute'),
  
  // Latency
  queueLatency: histogram('queue_latency_ms'),
  sendLatency: histogram('send_latency_ms'),
  
  // Errors
  errorRate: gauge('error_rate'),
  bounceRate: gauge('bounce_rate'),
  
  // Queue health
  queueDepth: gauge('queue_depth'),
  oldestJob: gauge('oldest_job_age_seconds')
};

Umbrales de alerta

const alerts = [
  {
    name: 'high_queue_depth',
    condition: 'queue_depth > 100000',
    severity: 'warning'
  },
  {
    name: 'high_error_rate',
    condition: 'error_rate > 0.05',
    severity: 'critical'
  },
  {
    name: 'slow_processing',
    condition: 'queue_latency_p99 > 60000',
    severity: 'warning'
  }
];

Consideraciones de base de datos

Almacenamiento eficiente

// Partition by date for easy cleanup
const emailLogSchema = {
  tableName: 'email_logs',
  partitionBy: 'RANGE (created_at)',
  indexes: [
    'user_id',
    'campaign_id',
    'status',
    'created_at'
  ],
  retention: '90 days'
};

// Archive old data
async function archiveOldEmails() {
  await db.query(`
    INSERT INTO email_logs_archive
    SELECT * FROM email_logs
    WHERE created_at < NOW() - INTERVAL '90 days'
  `);
  
  await db.query(`
    DELETE FROM email_logs
    WHERE created_at < NOW() - INTERVAL '90 days'
  `);
}

Mejores prácticas

  1. Prioriza las colas - Emails críticos primero
  2. Respeta los límites de tasa - Tanto del proveedor como del ISP
  3. Monitoriza todo - Profundidad de cola, latencia, errores
  4. Planifica para fallos - Reintentos, colas de dead letter
  5. Escala horizontalmente - Añade workers, no servidores más grandes
  6. Procesa por lotes de forma eficiente - Agrupa por plantilla y dominio
  7. Archiva agresivamente - No dejes que los logs crezcan para siempre

El email de alto volumen va de una entrega consistente y fiable a escala. Construye para el volumen que esperas, pero diseña para un crecimiento de 10x.

e_

Escrito por el equipo de emailr

Construyendo infraestructura de email para desarrolladores

¿Listo para empezar a enviar?

Obtén tu clave API y envía tu primer email en menos de 5 minutos. No se requiere tarjeta de crédito.