emailr_
Todos os artigos
usecase·10 min

E-mail multi-tenant: padrões de arquitetura

saasmulti-tenantarchitecture

Plataformas SaaS multi-tenant precisam de sistemas de e-mail que isolem os tenants enquanto compartilham a infraestrutura de forma eficiente. Veja como projetar e-mail para multi-tenancy.

Desafios de multi-tenant

  • Isolamento de tenants (reputação, dados)
  • Domínios de envio personalizados por tenant
  • Limites de taxa e cotas por tenant
  • Templates e branding específicos por tenant
  • Faturamento e analytics consolidados

Padrões de arquitetura

Isolamento de tenant

interface TenantEmailConfig {
  tenantId: string;
  sendingDomain: string;
  fromAddress: string;
  replyTo?: string;
  
  // Authentication
  dkim: {
    selector: string;
    privateKey: string;
  };
  
  // Limits
  dailyLimit: number;
  rateLimit: number; // per second
  
  // Branding
  templates: Record<string, string>;
  brandColors: {
    primary: string;
    secondary: string;
  };
}


### Envio com contexto de tenant

```typescript
async function sendTenantEmail(tenantId: string, email: Email) {
  const config = await getTenantEmailConfig(tenantId);
  
  return sendEmail({
    ...email,
    from: config.fromAddress,
    replyTo: config.replyTo,
    headers: {
      'X-Tenant-ID': tenantId
    }
  });
}

Domínios de envio personalizados

Fluxo de verificação de domínio

async function setupTenantDomain(tenantId: string, domain: string) {
  // Generate DNS records
  const records = {
    spf: {
      type: 'TXT',
      name: domain,
      value: 'v=spf1 include:spf.emailr.dev ~all'
    },
    dkim: {
      type: 'TXT',
      name: `emailr._domainkey.${domain}`,
      value: await generateDKIMRecord(tenantId)
    },
    dmarc: {
      type: 'TXT',
      name: `_dmarc.${domain}`,
      value: 'v=DMARC1; p=quarantine; rua=mailto:[email protected]'
    }
  };
  
  await savePendingDomain(tenantId, domain, records);
  
  return records;
}

Verificação de domínio

async function verifyTenantDomain(tenantId: string, domain: string) {
  const pending = await getPendingDomain(tenantId, domain);
  
  const results = await Promise.all([
    verifyDNSRecord(pending.records.spf),
    verifyDNSRecord(pending.records.dkim),
    verifyDNSRecord(pending.records.dmarc)
  ]);
  
  if (results.every(r => r.verified)) {
    await activateDomain(tenantId, domain);
    return { status: 'verified' };
  }
  
  return { status: 'pending', failed: results.filter(r => !r.verified) };
}

Limitação de taxa por tenant

class TenantRateLimiter {
  async checkLimit(tenantId: string): Promise<boolean> {
    const config = await getTenantConfig(tenantId);
    const usage = await getTenantUsage(tenantId, 'day');
    
    return usage.sent < config.dailyLimit;
  }
  
  async trackSend(tenantId: string, count: number = 1) {
    await redis.hincrby(`tenant:${tenantId}:usage`, 'sent', count);
  }
}

Gerenciamento de templates

Sobrescrições de templates por tenant

async function getTemplate(tenantId: string, templateId: string) {
  // Check for tenant override
  const tenantTemplate = await db.query(`
    SELECT * FROM tenant_templates
    WHERE tenant_id = $1 AND template_id = $2
  `, [tenantId, templateId]);
  
  if (tenantTemplate) {
    return tenantTemplate;
  }
  
  // Fall back to default
  return getDefaultTemplate(templateId);
}

Injeção de branding

async function applyTenantBranding(html: string, tenantId: string) {
  const branding = await getTenantBranding(tenantId);
  
  return html
    .replace('&#123;&#123;logo_url&#125;&#125;', branding.logoUrl)
    .replace('&#123;&#123;primary_color&#125;&#125;', branding.primaryColor)
    .replace('&#123;&#123;company_name&#125;&#125;', branding.companyName);
}

Isolamento de reputação

IPs dedicados por tenant

interface TenantIPConfig {
  tenantId: string;
  ipType: 'shared' | 'dedicated';
  dedicatedIPs?: string[];
  warmupStatus?: 'warming' | 'ready';
}

async function getIPForTenant(tenantId: string): Promise<string> {
  const config = await getTenantIPConfig(tenantId);
  
  if (config.ipType === 'dedicated' && config.warmupStatus === 'ready') {
    return selectIP(config.dedicatedIPs);
  }
  
  return getSharedPoolIP();
}

Analytics por tenant

async function getTenantAnalytics(tenantId: string, period: string) {
  return db.query(`
    SELECT 
      COUNT(*) as sent,
      SUM(CASE WHEN delivered THEN 1 ELSE 0 END) as delivered,
      SUM(CASE WHEN opened THEN 1 ELSE 0 END) as opened,
      SUM(CASE WHEN clicked THEN 1 ELSE 0 END) as clicked,
      SUM(CASE WHEN bounced THEN 1 ELSE 0 END) as bounced
    FROM email_events
    WHERE tenant_id = $1
    AND created_at > NOW() - $2::interval
  `, [tenantId, period]);
}

Boas práticas

  1. Isolar reputação - Um tenant ruim não deve afetar os demais
  2. Aplicar limites - Cotas e limitações de taxa por tenant
  3. Suportar domínios personalizados - Aparência profissional para os tenants
  4. Centralizar templates - Com capacidade de sobrescrita por tenant
  5. Acompanhar por tenant - Analytics, faturamento, uso
  6. Planejar para escala - Projetar para milhares de tenants

E-mail multi-tenant é sobre equilíbrio: infraestrutura compartilhada para eficiência, isolamento para segurança e personalização.

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.