npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@groundbrick/email-service

v1.4.0

Published

Serviço de Email plugável com suporte a múltiplos provedores (GatewayAPI, Sweego, SMTP) e failover

Readme

@groundbrick/email-service

Serviço de email plugável com suporte a múltiplos provedores (GatewayAPI, SMTP, etc.) para TypeScript/Node.js.

Características

  • 📧 Múltiplos Providers: GatewayAPI, Sweego, SMTP, DuckFlow, Mock, Redirect
  • 🛟 Failover: provedor de reserva (ex.: SMTP) sem risco de envio duplicado
  • 🔄 Múltiplas Instâncias: Diferente do sms-service, permite múltiplas configurações
  • 🎯 Webhooks: Suporte completo para webhooks de entrega
  • 📝 Templates: Variáveis dinâmicas em HTML/texto/subject
  • 📊 Tracking: Rastreamento de aberturas e cliques (GatewayAPI)
  • 📎 Anexos: Suporte a anexos com base64
  • 🔒 Type-Safe: 100% TypeScript com tipos completos
  • 🧪 Testável: Mock e Redirect providers para desenvolvimento

Instalação

npm install @groundbrick/email-service
# ou
pnpm add @groundbrick/email-service

Uso Básico

GatewayAPI Provider (Produção)

import { EmailService } from '@groundbrick/email-service';

const emailService = new EmailService({
  provider: 'gatewayapi',
  config: {
    token: process.env.GATEWAYAPI_EMAIL_TOKEN!,
    defaultFrom: {
      address: '[email protected]',
      name: 'My App'
    },
    webhookSecret: process.env.GATEWAYAPI_WEBHOOK_SECRET // opcional
  }
});

// Enviar email simples
const result = await emailService.send({
  to: { address: '[email protected]', name: 'John Doe' },
  subject: 'Welcome!',
  html: '<h1>Hello!</h1><p>Welcome to our app.</p>',
  text: 'Hello! Welcome to our app.'
});

console.log(result.messageId); // "123456"

Sweego Provider (Produção)

import { EmailService } from '@groundbrick/email-service';

const sweegoEmailService = new EmailService({
  provider: 'sweego',
  config: {
    apiKey: process.env.SWEEGO_API_KEY!, // ou defina accessToken para usar Bearer
    defaultFrom: {
      address: '[email protected]',
      name: 'My App'
    },
    clientId: process.env.SWEEGO_CLIENT_ID, // opcional, repassa para o cabeçalho client-id
    webhookSecret: process.env.SWEEGO_WEBHOOK_SECRET // opcional, Base64 para validar webhooks
  }
});

await sweegoEmailService.send({
  to: { address: '[email protected]', name: 'John Doe' },
  subject: 'Welcome!',
  html: '<h1>Hello!</h1><p>Welcome to our app.</p>',
  metadata: {
    sweego: {
      campaignId: 'welcome-2025',
      campaignTags: ['welcome'],
      dryRun: false
    }
  }
});

Use a chave metadata.sweego para acessar recursos específicos da API da Sweego (campanhas, headers customizados, list-unsubscribe, variáveis por destinatário, etc.). Veja a seção Sweego para a lista completa dos campos suportados.

Mock Provider (Testes)

const emailService = new EmailService({
  provider: 'mock',
  config: {
    defaultFrom: { address: '[email protected]' },
    simulateDelay: 100, // ms
    simulateFailures: false // ou true para 10% de falhas
  }
});

const result = await emailService.send({...});

// Acessar emails enviados (apenas Mock)
import { MockEmailProvider } from '@groundbrick/email-service';
const mock = emailService['provider'] as MockEmailProvider;
const sentEmails = mock.getSentEmails();

Redirect Provider (Desenvolvimento)

Redireciona TODOS os emails para um endereço específico:

const emailService = new EmailService({
  provider: 'redirect',
  config: {
    redirectTo: '[email protected]',
    defaultFrom: { address: '[email protected]' },
    addOriginalToSubject: true // adiciona destinatários originais no subject
  }
});

// Este email será redirecionado para [email protected]
await emailService.send({
  to: { address: '[email protected]' },
  subject: 'Order Confirmation',
  html: '<p>Your order is confirmed!</p>'
});
// Subject real: "[REDIRECTED] Order Confirmation (Original: [email protected])"

⚠️ Sem via (ou baseProvider) nada é entregue: os e-mails redirecionados vão para o Mock. O resultado indica providerType: 'mock'. Para entregar de facto, declare o provedor real em via (funciona em configurações JSON):

const emailService = new EmailService({
  provider: 'redirect',
  config: {
    redirectTo: '[email protected]',
    defaultFrom: { address: '[email protected]' },
    via: { provider: 'sweego', config: { apiKey: '...', defaultFrom: { address: '[email protected]' } } }
  }
});

SMTP Provider

const emailService = new EmailService({
  provider: 'smtp',
  config: {
    host: 'smtp.example.com',
    port: 587,          // 465 com secure: true
    secure: false,      // TLS implícito (465); em 587 usa STARTTLS
    requireTLS: true,   // padrão: recusa enviar sem TLS (credenciais e conteúdo)
    user: 'apikey',
    password: '...',
    timeoutMs: 15000,
    defaultFrom: { address: '[email protected]', name: 'App' }
  }
});

Não há webhooks de entrega: uma mensagem aceite pelo servidor fica SENT.

DuckFlow Provider

Envia pela fila do DuckFlow, que guarda as credenciais do fornecedor real e o remetente por omissão na config do cliente. Usa o @groundbrick/duckflow-client.

const emailService = new EmailService({
  provider: 'duckflow',
  config: {
    baseUrl: process.env.DUCKFLOW_BASE_URL,        // sem /api/v1
    apiKey: process.env.DUCKFLOW_API_KEY,
    callbackUrl: process.env.DUCKFLOW_CALLBACK_URL, // recebe task.failed e task.delivery
    notifyDelivery: true
  }
});

const result = await emailService.send(message);
result.messageId; // externalId da tarefa, o mesmo que vem nos callbacks
  • success: true quer dizer que o DuckFlow aceitou a tarefa, não que o fornecedor aceitou o email. A recusa, a entrega e as devoluções chegam depois, no callback assinado.
  • message.idempotencyKey segue como Idempotency-Key: com uma chave estável (ex.: o id da linha que regista o envio) a mesma mensagem nunca sai duas vezes. Timeouts e falhas do DuckFlow são repetidos com a mesma chave antes de o provider desistir.
  • Só vão os endereços: a API do DuckFlow não leva nomes em to, cc, bcc, from.
  • Anexos em Buffer seguem em base64 com encoding: 'base64'; texto segue como está.
  • failureKind segue a tabela do failover abaixo: unknown quer dizer que a tarefa pode ter sido criada.

Failover Provider (reserva)

Tenta os provedores por ordem e passa ao seguinte só quando a falha garante que nada foi enviado — nunca há envio duplicado:

const emailService = new EmailService({
  provider: 'failover',
  config: {
    providers: [
      { provider: 'sweego', config: { apiKey: '...', defaultFrom } },
      { provider: 'smtp', config: { host: 'smtp.example.com', user: '...', password: '...', defaultFrom } }
    ]
  }
});

const result = await emailService.send(message);
result.providerType; // 'sweego' ou 'smtp': quem enviou de facto
result.attempts;     // [{ provider: 'sweego', success: false, failureKind: 'unavailable', ... }, ...]

Cada falha traz failureKind:

| failureKind | Quando | Passa à reserva? | |---|---|---| | network | Não foi possível ligar ao provedor (recusado, DNS, TLS) | Sim | | unavailable | HTTP 5xx/429 (exceto 504/524), SMTP 4xx: o provedor não aceitou agora | Sim | | rejected | HTTP 4xx, SMTP 5xx, credenciais: recusou esta mensagem | Não | | unknown | Timeout à espera da resposta, HTTP 504/524 (proxy esgotou o tempo): pode ter sido aceite | Não (evita duplicar) |

Os webhooks de entrega (validateWebhook) são os do primeiro provedor.

Funcionalidades Avançadas

Múltiplos Destinatários (CC, BCC)

await emailService.send({
  to: [
    { address: '[email protected]', name: 'User 1' },
    { address: '[email protected]' }
  ],
  cc: [{ address: '[email protected]' }],
  bcc: [{ address: '[email protected]' }],
  subject: 'Meeting Tomorrow',
  html: '<p>Don\'t forget our meeting!</p>'
});

Anexos

await emailService.send({
  to: { address: '[email protected]' },
  subject: 'Invoice #1234',
  html: '<p>Your invoice is attached.</p>',
  attachments: [
    {
      filename: 'invoice.pdf',
      content: pdfBuffer, // Buffer ou string
      contentType: 'application/pdf'
    }
  ]
});

Templates com Variáveis

await emailService.send({
  to: { address: '[email protected]' },
  subject: 'Hello %firstname!',
  html: '<p>Hi %firstname %lastname, your code is %code.</p>',
  template: {
    variables: {
      firstname: 'John',
      lastname: 'Doe',
      code: '123456'
    }
  }
});

Tracking de Opens/Clicks (GatewayAPI)

await emailService.send({
  to: { address: '[email protected]' },
  subject: 'Newsletter',
  html: '<p>Check out our <a href="https://example.com">website</a>!</p>',
  tracking: {
    opens: true,
    clicks: true
  }
});

Metadados específicos do Sweego

Quando estiver usando o provider sweego, utilize metadata.sweego para ativar opções exclusivas da API:

await sweegoEmailService.send({
  to: { address: '[email protected]' },
  subject: 'Welcome',
  html: '<p>Hello!</p>',
  metadata: {
    sweego: {
      campaignId: 'welcome-2025',
      campaignTags: ['welcome', 'prod'],
      campaignType: 'transac',
      headers: { 'x-custom-header': 'value' },
      listUnsub: { method: 'one-click', value: 'mailto:[email protected],https://example.com/unsub' },
      dryRun: false,
      expires: '2025-12-31T23:59:59Z',
      clientId: 'my-client',
      variables: { name: 'John' }
    }
  }
});

Campos suportados em metadata.sweego:

  • campaignId, campaignTags e campaignType
  • headers (até 5 cabeçalhos customizados)
  • listUnsub ({ method?: 'mailto' | 'one-click'; value: string })
  • dryRun e expires
  • variables ou perRecipientVariables (array de objetos na mesma ordem dos destinatários)
  • clientId, channel e provider (casos multi-provedor na mesma conta)

Delivery Reports

const result = await emailService.send({...});

// Consultar status depois
const report = await emailService.getDeliveryReport(result.messageId!);

console.log(report?.status); // 'delivered', 'bounced', 'failed', etc.

Webhooks

// Middleware para manter o raw body (necessário para Sweego)
const rawJson = express.json({
  verify: (req, _res, buf) => {
    req.rawBody = buf.toString('utf8');
  }
});

app.post('/api/webhooks/email', rawJson, (req, res) => {
  const signature = req.headers['authorization'] || '';

  const validation = emailService.validateWebhook(req.body, signature, {
    headers: req.headers as Record<string, string | string[]>,
    rawBody: req.rawBody // Sweego precisa do corpo original para validar o HMAC
  });

  if (validation.isValid && validation.payload) {
    const report = validation.payload;
    console.log(`Email ${report.messageId} status: ${report.status}`);
  }

  res.sendStatus(200);
});

💡 Sweego: defina webhookSecret (string em Base64) no config e garanta que as headers webhook-id, webhook-timestamp e webhook-signature sejam repassadas para validateWebhook através do parâmetro headers.

Configuração por Ambiente

const emailService = new EmailService({
  provider: process.env.NODE_ENV === 'production' ? 'gatewayapi' : 'redirect',
  config: process.env.NODE_ENV === 'production'
    ? {
        token: process.env.GATEWAYAPI_EMAIL_TOKEN!,
        defaultFrom: { address: '[email protected]', name: 'MyApp' }
      }
    : {
        redirectTo: process.env.DEV_EMAIL || '[email protected]',
        defaultFrom: { address: '[email protected]', name: 'MyApp [DEV]' }
      }
});

API Reference

EmailService

class EmailService {
  constructor(config: EmailConfig)
  send(message: EmailMessage): Promise<EmailResult>
  getDeliveryReport(messageId: string): Promise<DeliveryReport | null>
  validateWebhook(
    payload: any,
    signature?: string,
    options?: WebhookValidationOptions
  ): WebhookValidationResult
}
interface WebhookValidationOptions {
  headers?: Record<string, string | string[] | undefined>;
  rawBody?: string;
}

EmailMessage

interface EmailMessage {
  to: EmailAddress | EmailAddress[]
  from?: EmailAddress
  cc?: EmailAddress[]
  bcc?: EmailAddress[]
  replyTo?: EmailAddress
  subject: string
  text?: string
  html?: string
  attachments?: EmailAttachment[]
  template?: EmailTemplate
  tracking?: EmailTracking
  metadata?: Record<string, any>
}

EmailStatus

enum EmailStatus {
  QUEUED = 'queued',
  SENT = 'sent',
  DELIVERED = 'delivered',
  BOUNCED = 'bounced',
  FAILED = 'failed',
  DEFERRED = 'deferred',
  BLOCKED = 'blocked'
}

GatewayAPI

Para usar o GatewayAPI:

  1. Obtenha um token em GatewayAPI
  2. Configure webhook (opcional) para receber notificações de entrega
  3. Use o token no config:
const emailService = new EmailService({
  provider: 'gatewayapi',
  config: {
    token: 'your-token-here',
    baseUrl: 'https://gatewayapi.com', // opcional
    webhookSecret: 'your-webhook-secret', // opcional, para validação JWT
    defaultFrom: { address: '[email protected]' }
  }
});

Limites GatewayAPI

  • Anexos: Máximo 5MB total
  • Rate limits: Conforme seu plano

Sweego

  1. Crie um API Key ou OAuth Client em app.sweego.io
  2. Configure e verifique um subdomínio dedicado para envio
  3. Copie o segredo do webhook (menu Webhooks) e salve a versão Base64 no webhookSecret
const emailService = new EmailService({
  provider: 'sweego',
  config: {
    apiKey: process.env.SWEEGO_API_KEY!, // ou use accessToken
    clientId: process.env.SWEEGO_CLIENT_ID,
    defaultFrom: { address: '[email protected]', name: 'My App' },
    webhookSecret: process.env.SWEEGO_WEBHOOK_SECRET
  }
});

Opções suportadas

  • apiKey ou accessToken (Bearer)
  • clientId para definir o cabeçalho client-id
  • baseUrl, channel e providerSlug (casos de sandbox)
  • webhookSecret (Base64) para validar webhook-id/webhook-timestamp/webhook-signature

Use metadata.sweego para configurar campanhas, headers, dry-run, variáveis e list-unsubscribe. Documentação oficial: envio por API e assinatura de webhook.

Migração do Service-Base

Veja MIGRATION.md para guia completo de migração do antigo @groundbrick/service-base.

Licença

MIT