Portfólio Portfolio

Estudo de caso Case study

Seslock Holmes

Um dashboard de investigação para o AWS SES que transformou bounce em diagnóstico — e diagnóstico em correção. A taxa de bounce de um provedor caiu de 11,6% para 0,8%. An investigation dashboard for AWS SES that turned bounces into diagnoses — and diagnoses into fixes. One provider's bounce rate dropped from 11.6% to 0.8%.

Papel
Role
Desenvolvimento, diagnóstico e operação
Development, diagnostics and operations
Contexto
Context
Ramada Atacadista
Situação
Status
Em produção
In production
Stack
React · TypeScript · Supabase · AWS SES · Vercel

A demo pede um projeto Supabase configurado para exibir dados. The demo needs a configured Supabase project to show data.

O problema The problem

O AWS SES emite um evento para cada coisa que acontece com um e-mail: entrega, bounce, reclamação, rejeição. Só que esses eventos chegam soltos. Para quem está no suporte, a informação que sobra é a pior possível: “o e-mail não chegou”.

Sem uma leitura consolidada não dá para separar as causas. Endereço digitado errado, domínio sem registro MX e falha de autenticação DMARC produzem sintomas parecidos e exigem correções completamente diferentes.

E bounce alto não é só incômodo operacional: ele degrada a reputação de envio. Quanto pior a reputação, mais mensagens legítimas caem em spam — até o provedor começar a bloquear o envio.

AWS SES emits an event for everything that happens to an email: delivery, bounce, complaint, rejection. Those events arrive scattered. For whoever is on support duty, the only information left is the worst possible one: “the email didn't arrive”.

Without a consolidated view you cannot separate causes. A mistyped address, a domain with no MX record and a DMARC authentication failure produce similar symptoms and require completely different fixes.

And a high bounce rate is not just an operational annoyance: it degrades sending reputation. The worse the reputation, the more legitimate mail lands in spam — until the provider starts blocking delivery altogether.

A abordagem The approach

Construí um painel somente-leitura sobre os eventos já armazenados em Supabase/PostgreSQL. A regra central: o dashboard nunca cria, edita ou remove evento de e-mail — ele só lê, e a leitura depende de RLS e autenticação no banco.

  • Indicadores calculados no banco, via RPC, para refletir o período inteiro filtrado em vez de uma amostra carregada no navegador.
  • Investigação por eixo: destinatário, remetente, provedor (domínio do destinatário) ou diagnóstico de bounce, com sugestões de e-mails semelhantes quando não há correspondência exata.
  • Rastreamento cronológico da mensagem, com origem/IP, tempo de processamento, resposta SMTP, tipo e subtipo de bounce e diagnostic code direto no card.
  • Relatórios agendados com filtros próprios e múltiplos destinatários, disparados por um workflow do GitHub Actions a cada 15 minutos e reforçados por um cron diário na Vercel.
  • Compartilhamento público somente-leitura, com os filtros travados e protegido por token de 256 bits, para quem precisa ver o painel sem ter conta.

I built a read-only panel on top of the events already stored in Supabase/PostgreSQL. The core rule: the dashboard never creates, edits or deletes an email event — it only reads, and that read depends on RLS and authentication in the database.

  • Metrics computed in the database, through RPCs, so they reflect the entire filtered period instead of a sample loaded in the browser.
  • Investigation by axis: recipient, sender, provider (recipient domain) or bounce diagnosis, with similar-address suggestions when there is no exact match.
  • Chronological message tracing, exposing source/IP, processing time, SMTP response, bounce type and subtype and the diagnostic code right on the card.
  • Scheduled reports with their own filters and multiple recipients, fired by a GitHub Actions workflow every 15 minutes and backed by a daily Vercel cron.
  • Public read-only sharing, with filters locked and protected by a 256-bit token, for people who need to see the panel without an account.

Resultados Results

O número que mudou. The number that moved.

Taxa de bounce, antes e depois Bounce rate, before and after

Antes Before Depois After

Gráfico de barras da taxa de bounce antes e depois do Seslock Holmes. Em um provedor, caiu de 11,6% para 0,8%. No cenário geral, caiu de 6,8% para 1,8%. Bar chart of the bounce rate before and after Seslock Holmes. For one provider it fell from 11.6% to 0.8%. Overall it fell from 6.8% to 1.8%.

0% 3% 6% 9% 12% 11,6% 0,8% 6,8% 1,8% Um provedor One provider Cenário geral Overall
-93% bounce em um provedor (11,6% → 0,8%) bounce for one provider (11.6% → 0.8%)
-74% bounce no cenário geral (6,8% → 1,8%) bounce overall (6.8% → 1.8%)
Reputação Reputation menos spam e menor risco de bloqueio less spam and lower blocking risk

As duas séries usam cores distinguíveis nas formas mais comuns de daltonismo, e a barra “depois” também é hachurada — nenhuma leitura do gráfico depende só de cor. Todos os valores estão escritos ao lado das barras. The two series use colors that stay distinguishable under the most common forms of color vision deficiency, and the “after” bar is also hatched — no reading of this chart depends on color alone. Every value is written next to its bar.

O número só se moveu porque cada bounce virou uma ação concreta: endereços inválidos removidos da base, domínios sem registro MX corrigidos e falhas de autenticação DMARC ajustadas. O painel não conserta entregabilidade sozinho — ele mostra onde consertar.
The number only moved because every bounce became a concrete action: invalid addresses removed from the base, domains without MX records fixed, and DMARC authentication failures corrected. The panel does not fix deliverability by itself — it shows where to fix it.

Como está construído How it is built

Frontend

React com TypeScript sobre Vite, organizado por feature e com i18n PT/EN.

React with TypeScript on Vite, organised by feature, with PT/EN i18n.

Dados

Data

Supabase/PostgreSQL com RLS e RPCs de analytics; migrations versionadas no repositório.

Supabase/PostgreSQL with RLS and analytics RPCs; migrations versioned in the repository.

Envio de relatórios

Report delivery

Funções serverless na Vercel, fora do navegador, com token de administração próprio.

Serverless functions on Vercel, outside the browser, with their own admin token.

Agendamento

Scheduling

GitHub Actions a cada 15 minutos, com cron diário da Vercel como reforço.

GitHub Actions every 15 minutes, with a daily Vercel cron as backup.

Segurança e acesso Security and access

Um painel que lê e-mail de cliente é um painel que precisa errar pouco. As decisões de acesso vieram antes das telas:

  • O frontend usa apenas a URL e a chave pública do Supabase — nunca uma service key.
  • Perfis de acesso: toda conta é viewer ou manager. Viewers só visualizam; managers também criam e editam agendamentos e links. Conta nova começa como viewer.
  • Log de auditoria somente-leitura, visível a qualquer usuário autenticado, com toda ação administrativa: criação, edição, pausa, exclusão, disparo forçado e resultado dos envios.
  • Nos links públicos, a lista de atividade recente vem desligada por padrão, porque ela expõe e-mail de destinatário. O link tem validade opcional e pode ser regenerado ou revogado.

A panel that reads customer email is a panel that needs a small blast radius. Access decisions came before the screens:

  • The frontend only uses the Supabase URL and publishable key — never a service key.
  • Access profiles: every account is a viewer or a manager. Viewers only read; managers also create and edit schedules and links. New accounts start as viewers.
  • Read-only audit log, visible to any authenticated user, covering every administrative action: creation, edit, pause, deletion, forced runs and the outcome of each automated send.
  • On public links the recent-activity list is off by default, because it exposes recipient addresses. Links have an optional expiry and can be regenerated or revoked.

O que eu levo disso What I took from it

  • Indicador calculado no cliente mente. Enquanto a taxa de bounce saía de uma amostra paginada, ela mudava conforme a rolagem. Empurrar o cálculo para RPC no banco resolveu o número e a confiança nele.
  • Bounce não é um número, é uma lista de causas. O painel só ficou útil quando passou a separar subtipo — suppressed, general, mailbox full, content rejected, undetermined.
  • Compartilhar exige desenhar o que não vai junto. A parte difícil do link público não foi gerar o token: foi decidir que a atividade recente fica fora por padrão.
  • A metric computed in the client lies. While the bounce rate came from a paginated sample, it changed as you scrolled. Pushing the computation into a database RPC fixed both the number and the trust in it.
  • A bounce is not a number, it is a list of causes. The panel only became useful once it separated subtypes — suppressed, general, mailbox full, content rejected, undetermined.
  • Sharing means designing what stays out. The hard part of the public link was not generating the token: it was deciding that recent activity is excluded by default.

Quer ver o resto do trabalho? Want to see the rest of the work?

Tem mais projeto, experiência e formas de falar comigo do outro lado deste link. There are more projects, experience and ways to reach me on the other side.