emailr_
Todos os artigos
usecase·11 min

E-mail de alto volume: escalando para milhões por dia

scaleinfrastructurearchitecture

Enviar milhões de e-mails por dia exige uma arquitetura cuidadosa. O gerenciamento de filas, a limitação de taxa e o design da infraestrutura impactam a entregabilidade e a confiabilidade. Veja como construir para escala.

Visão geral da arquitetura

Componentes principais

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

Design da fila

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
};

Limitação de taxa

Limites do provedor

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 dos ISPs

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;
}

Escalonamento de workers

Escalonamento 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)))
    );
  }
}

Gatilhos de autoescalonamento

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
  }
};

Processamento em lote

Loteamento 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 };
}

Monitoramento em escala

Métricas principais

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')
};

Limiares 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'
  }
];

Considerações de banco de dados

Armazenamento 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'
  `);
}

Boas práticas

  1. Priorize as filas - E-mails críticos primeiro
  2. Respeite os limites de taxa - Tanto do provedor quanto do ISP
  3. Monitore tudo - Profundidade da fila, latência, erros
  4. Planeje para falhas - Retries, dead letter queues
  5. Escalone horizontalmente - Adicione workers, não servidores maiores
  6. Faça lotes com eficiência - Agrupe por template, domínio
  7. Arquive agressivamente - Não deixe os logs crescerem para sempre

E-mail de alto volume é sobre entrega consistente e confiável em escala. Construa para o volume que você espera, mas projete para um crescimento de 10x.

e_

Escrito pela equipe emailr

Construindo infraestrutura de email para desenvolvedores

Pronto para começar a enviar?

Obtenha sua chave API e envie seu primeiro email em menos de 5 minutos. Não é necessário cartão de crédito.