Olá! Sou Joseph, estudante de Ciência da Computação em Waterloo, e recentemente concluí um estágio em Engenharia de Software na Slash, de setembro a dezembro de 2025. Resumindo este blog: me diverti muito e acho que você deveria participar. Obrigado pela leitura.

Ok, desta vez é para valer. Já estagiei em várias empresas, desde pequenas startups relacionadas à música até grandes empresas de tecnologia, mas a Slash ofereceu uma combinação única de intensidade e autonomia que eu não encontrei em nenhum outro lugar. A equipe era inteligente e unida, e eu aprendi profundamente sobre muitos tópicos complexos, desde a manutenção do Kubernetes até o desempenho do banco de dados. Mais importante ainda, havia alguns desafios realmente interessantes que eu pude resolver, e foi por isso que decidi escrever um blog sobre minhas experiências!

Para resumir rapidamente o que a Slash faz, eles começaram como uma plataforma financeira para pessoas como empreendedores e revendedores de tênis, mas agora são uma empresa de fintech bancária geral que oferece cartões corporativos, transferências internacionais, contas criptográficas e muito mais. Eu não sabia muito sobre isso quando conheci a Slash. Afinal, não sou um colecionador de tênis nem entendo muito de fintech, então passei muito tempo pensando se essa oportunidade valia a pena ou não. No entanto, como você pode ver por esta postagem no blog, minha escolha de entrar acabou sendo uma das melhores decisões que eu poderia ter tomado no início da minha carreira.

As principais razões pelas quais acabei escolhendo a Slash em vez das outras empresas foram:

  • Era uma empresa da série B. – Nunca trabalhei em uma startup desse porte e queria experimentar esse ambiente.
  • Eles estavam com uma receita anualizada de 150 milhões, o que é incrivelmente alto para uma empresa da série B. – eles não estavam apenas sobrevivendo ou se mantendo; eles estavam se expandindo ativamente. Como bônus adicional, isso significava que eles provavelmente tinham um bom DX e rigor de engenharia que muitas pequenas startups não têm.
  • Eles estavam crescendo ativamente. – Eles multiplicaram por seis a sua receita em um ano e recentemente levantaram a sua série B. Essa empresa estava em sua era de ouro em termos de escala, e eu queria trabalhar em uma empresa que estivesse passando por esse tipo de impulso.
  • É uma equipe jovem e cheia de energia. – Pelas entrevistas que fiz, percebi que as pessoas aqui eram tranquilas e agradáveis de se conversar. Fiquei animado para trabalhar com colegas com quem eu me identificava.
  • É uma empresa pequena e enxuta. – eu mencionei que essa receita anual recorrente (ARR) de 150 milhões foi alcançada por uma equipe de cerca de 12 engenheiros? Isso significa uma ARR de 12,5 milhões. por engenheiro, o que é incrível. É evidente que as pessoas da Slash são talentos de ponta, e eu teria tanto um alto nível de propriedade quanto uma excelente orientação.
  • A remuneração e os benefícios oferecidos por pequenas empresas são os melhores para estágios... 🤫

TLDR, achei que o ambiente, as pessoas talentosas e a energia das startups proporcionariam um lugar incrível para crescer. Imaginei que teria a chance de ser responsável por um projeto do início ao fim, desde a configuração da infraestrutura até o planejamento da escalabilidade, passando pelo design do sistema e muito mais, então aproveitei a oportunidade!

Projeto: Notificações V2

Aqui está uma coisa especial que aprendi imediatamente sobre a Slash: eles atribuem projetos importantes aos estagiários e confiam que eles são capazes de levá-los adiante. Na minha segunda semana, fui encarregado de reformular as notificações de recusa de transações com cartão. Não parece tão difícil, certo? No entanto, o sistema de notificações existente tinha alguns problemas:

  • Acoplamento estreito com nosso sistema interno de eventos, com lógica de negócios incorporada ao núcleo do serviço
  • Má qualidade das métricas de e-mail e pior capacidade de entrega de e-mails devido ao provedor que utilizamos.
  • Experiência difícil para desenvolvedores, o que levou os engenheiros a ignorar completamente o sistema e chamar diretamente as APIs dos provedores.

E assim surgiu a tarefa insidiosa: para criar e-mails de recusa melhores, precisávamos de mais do que um patch. Precisávamos de um sistema de notificações totalmente redesenhado, e eu me tornei o DRI (Responsável pela Execução) pelo design, desenvolvimento, lançamento e dimensionamento de todo o projeto. Isso era superintimidador... mas também uma oportunidade incrível de arquitetar algo que acabaria lidando com milhões de notificações. Eu estava animado para começar.

Ao estudar o sistema de notificação anterior e discutir com os engenheiros, descobri alguns princípios que este novo sistema deve seguir:

  1. Um design independente do evento – o serviço deve poder funcionar em qualquer lugar, seja em scripts únicos ou manipuladores de eventos
  2. Um design independente do fornecedor – a oportunidade de remover e introduzir novos fornecedores facilmente, com o mínimo de esforço, seria inestimável.
  3. Um design voltado para o desenvolvedor – sem mais frustrações. A experiência de desenvolvimento deve ser o mais intuitiva e fácil possível.
  4. Observável e escalável - as notificações devem ter rastreamento completo do ciclo de vida e, eventualmente, devemos ser capazes de suportar 3 milhões de e-mails por mês!

E a partir disso, surgiu o design baseado em manipuladores do Notifications V2. O sistema é centrado em manipuladores de notificações, que expõem funções comuns de manipuladores, como enviar notificações ou processar webhooks, para provedores externos específicos de canais (por exemplo, Resend para e-mails, Twilio para SMS).

No centro do sistema está o Notificação de intenção, um objeto transitório genérico usado para transportar todos os dados de uma notificação, como argumentos de provedor, canal e carga útil. Cada manipulador define a forma de suas próprias intenções, e esses objetos são fortemente tipados e, portanto, suportam verificações de tipo em tempo estático e introspecção. Isso significa que os desenvolvedores não precisam conhecer todas as complexidades de cada provedor — eles podem aproveitar o preenchimento automático do IDE e confiar que a verificação de tipo os levará adiante.

This example briefly illustrates how to use the service to send a variety of email notifications for an outgoing payment. All these create functions create notification intents, and they are strongly typed to suit the provider (in this case, Resend)
const sendOutgoingPaymentNotification = async (context: {
 transactionId: string;
 amount: number;
 recipientEmail: string;
 alertEmail: string;
}) => {
 const { transactionId, amount, recipientEmail, alertEmail } = context;
 const { originalSenderId } = getOriginalSenderForTransaction(transactionId);


 await notificationsV2Service.sendNotifications([
   // We can create a one-off notification easily...
   ResendNotification.create({
     email: {
       recipient: alertEmail,
       content: {
         subject: "INTERNAL ALERT: An employee just sent an outgoing payment",
         body: `An employee just sent an outgoing payment of $${amount}.`,
       },
     },
   }),
   // Or use a React Email template for pretty emails..
   ResendNotification.create({
     email: {
       recipient: recipientEmail,
       content: YouJustReceivedPaymentTemplate.withProps({ amount }),
     },
   }),
 ]);


 // Or send to specific users, respecting their preferences + registered emails
 await notificationsV2Service.sendNotificationsToUsers({
   users: [originalSenderId],
   preference: NotificationPreferences.PaymentConfirmation,
   createIntents: (user) => [
     ResendNotification.create({
       email: {
         recipient: user.registeredEmail,
         content: PaymentConfirmationTemplate.withProps({ amount }),
       },
     }),
   ],
 });
};

Para garantir que a lógica de negócios, como a verificação das preferências do usuário, fosse implementada de maneira pouco acoplada, assim que um desenvolvedor chama uma função de envio, as intenções passam por um NotificaçãoMiddleware primeiro pipeline. Os middlewares de notificação são funções de ordem superior que possuem uma etapa de pré-processamento para permitir pesquisas compartilhadas no banco de dados e uma função de processo que nos permite permitir, transformar ou descartar intenções de notificação. Com esse design, agora podemos definir diferentes funções de envio que usam um sistema subjacente para definir e compartilhar a lógica de validação. Por exemplo:

  • Podemos definir uma função básica de “enviar notificação” que tenha fluxos de intenções através de um DestinatárioBanido middleware, que descarta intenções se as informações do destinatário (e-mail, número de telefone) estiverem em uma lista de bloqueios personalizada
  • Podemos definir uma função “enviar ao usuário” mais complexa, que tenha fluxos de intenções através do mesmo DestinatárioBanido middleware, bem como um Respeitar as preferências do usuário middleware, que descarta intenções se o usuário tiver optado por não receber notificações nesse canal

Como benefício adicional, é fácil para futuros desenvolvedores adicionar, remover ou modificar novas funções de envio sem ter que rastrear um labirinto de funções.


Um exemplo de alguns fluxos que decidimos usar. Observe como sendNotificationToUsers e sendNotification usam o middleware heurístico, mas a função de envio do usuário executa validadores mais específicos do usuário.


Após a etapa do middleware, o serviço cria Notificação objetos do ciclo de vida antes de iniciar um fluxo de trabalho Temporal. O Temporal é excelente porque nos oferece tentativas integradas para falhas no fluxo de trabalho, estado durável e garantias de orquestração. Todos esses fatores são importantes para garantir a capacidade de entrega das notificações. O fluxo de trabalho enfileira as notificações, limita os envios para respeitar os limites de taxa do provedor e, assim, não enviamos spam aos usuários, e então passa os lotes para os manipuladores do provedor. Os manipuladores então assumem a responsabilidade de enviar a notificação ao provedor e o serviço atualiza o ciclo de vida de acordo.


Assim que um provedor externo nos envia uma resposta webhook, os manipuladores a convertem em um tipo de dados padronizado e emitem ações tipadas. As ações nos oferecem uma maneira estruturada e segura de responder a eventos ao receber uma notificação, mantendo o serviço o mais imparcial possível. Por exemplo,

  • Temos uma ação para atualizar destinatários – Dessa forma, os administradores podem atualizar os destinatários como “banidos permanentemente” ou não, dependendo do que o provedor e o canal determinarem como “banido”.
  • Temos uma ação para tentar novamente uma notificação – por exemplo, se um e-mail for temporariamente rejeitado, queremos tentar novamente o e-mail exponencialmente. Se o manipulador determinar que a resposta do provedor de e-mail foi “rejeitada”, ele pode acionar essa ação.

ProcessWebhook returns the same body structure, allowing specific webhook logic to be handled by the actual provider itself.
const statusMap: Record<string, string> = {
 bounced: "failed",
 // more statuses here...
};


const ResendHandler = createHandler({
 // This is a simple example of something we could do
 processWebhook: (webhookBody: ResendWebhook) => {
   const notificationId = extractIdFromWebhook(webhookBody);


   const newInternalStatus = statusMap[webhookBody.state];
  
   const actions = newInternalStatus === "failed"
     ? [{ action: "retryNotification", notificationId }]
     : [];


   return {
     status: newInternalStatus,
     actions,
   };
 },
});

Ufa! Isso é a maior parte do fluxo. Ao longo do caminho, deparei-me com vários outros desafios de engenharia interessantes, como

  • Suporte a repetições exponenciais – Programamos futuras repetições de tarefas no Temporal, dependendo do número de repetições já realizadas.
  • Boa rastreabilidade – Por exemplo, armazenamos IDs de rastreamento, bem como notificações e suas repetições, em um Conjunto de notificações entidade
  • Envio em lote – Integrar o envio individual e em lote nos permite evitar exceder os limites de taxa do provedor e dá aos desenvolvedores controle sobre como desejam que as notificações sejam enviadas.

Deixarei que os novos engenheiros em potencial descubram os detalhes no código, assim que ingressarem na equipe.

Projeto: Reestruturação

O Projeto Facelift foi a reformulação completa da experiência web do Slash pela nossa equipe (veja o blog de engenharia). Aqui por Albert Tian, o pai do lifting facial). Não vou entrar em detalhes sobre o que era o Facelift (mais uma vez, veja o excelente blog de engenharia), mas, basicamente, ele introduzia uma nova biblioteca de componentes + Tailwind em todo o nosso enorme front-end. Agora, para se preparar para o lançamento, a equipe de engenharia decidiu se divertir um pouco e realizar uma hackweek focada! No entanto, essa não foi uma hackweek típica no escritório — entramos em alguns carros, dirigimos três horas até uma cabana em Bass Lake, ao lado de Yosemite, e passamos a semana construindo, refatorando e nos divertindo genuinamente uns com os outros.

Durante essa semana, mudei totalmente do trabalho de back-end para ajudar a reformular as transações e os fluxos de disputas. Esse trabalho era principalmente de front-end, o que foi uma mudança revigorante em relação ao meu trabalho anterior e também significou que pude trabalhar em grande parte da nossa infraestrutura de design de front-end, como:

  • Primitivas essenciais: por exemplo, como geramos nossos painéis laterais, modais e pop-ups
  • Componentes reutilizáveis e combináveis: por exemplo, Pesquisa selecionável
  • Gerente de mesa: nosso componente personalizado para criar tabelas com fácil classificação, filtragem, paginação e pesquisa
  • Validação robusta de subformulários: utilizando Arktype, formulários Tanstack e nosso próprio sistema de geração de modelos

A renovação não teve tanto a ver com a lógica complexa do produto, mas sim com testemunhar a rapidez com que nossa equipe pode agir quando focada e alinhada em um projeto coletivo. Ver todo o site se transformar ao longo da semana, além de construir e criar laços com meus colegas na cabana, foi definitivamente um dos destaques do meu estágio. O trabalho de renovação não terminou naquela semana – nas semanas seguintes, garantimos que o lançamento ocorresse sem problemas, trabalhando com o suporte para eliminar pequenos detalhes do produto!

Projeto: Modelos

No final de cada período letivo, minha universidade costuma enviar um pequeno formulário com uma pergunta simples: “Em que medida os conteúdos do curso foram aplicáveis ao seu trabalho?”. Bem, durante meu tempo na Slash, eu realmente pude aplicar alguns conceitos que aprendi em sala de aula diretamente! Veja bem, queríamos que nossos e-mails transacionais combinassem com nosso novo design Facelift, então fazia sentido que nosso sistema de notificações suportasse modelos personalizados. Para garantir que usássemos as mesmas cores e espaçamento do nosso front-end, acabamos criando esses modelos em código usando o React Email, uma biblioteca de modelos de e-mail que nos permite usar JSX para esses modelos. Isso foi perfeito para nós; como nossa equipe está intimamente familiarizada com o React JSX, pudemos garantir a consistência da marca usando o mesmo sistema de design Tailwind CSS v4 tanto na web quanto no e-mail.

No entanto, havia um porém: uma das principais vantagens do TailwindCSS 4 é a capacidade de usar arquivos CSS personalizados em vez do tradicional objeto de configuração JS, que nossa equipe aproveitou. No entanto, o componente Tailwind do React Email na verdade... necessário o objeto de configuração JS.

Isso significava que eu tinha que construir um transpiler!

@import "tailwindcss";

@import "../custom-styles.css";

@custom-variant dark (&:where(.dark, .dark *):not(:where(.light, .light *)));

@theme {
  --color-*: initial;

  --spacing-base-scale-0: 0px;
  --spacing-base-scale-1: 1px;
  --spacing-base-scale-2: 2px;
  --spacing-base-scale-4: 4px;
  --spacing-base-scale-6: 6px;
  --spacing-base-scale-8: 8px;
  --spacing-base-scale-12: 12px;
  --spacing-base-scale-14: 14px;
  --spacing-base-scale-16: 16px;
  --spacing-base-scale-20: 20px;
  --spacing-base-scale-24: 24px;
  --spacing-base-scale-28: 28px;
  --spacing-base-scale-32: 32px;
  --spacing-base-scale-36: 36px;
  --spacing-base-scale-40: 40px;
  --spacing-base-scale-44: 44px;
  --spacing-base-scale-48: 48px;
  --spacing-base-scale-52: 52px;
  --spacing-base-scale-56: 56px;
  --spacing-base-scale-60: 60px;
  --spacing-base-scale-64: 64px;
  --spacing-base-scale-68: 68px;
  --spacing-base-scale-72: 72px;
  --spacing-base-scale-76: 76px;
  --spacing-base-scale-80: 80px;
  --spacing-base-scale-84: 84px;
  --spacing-base-scale-88: 88px;
  --spacing-base-scale-92: 92px;
  --spacing-base-scale-96: 96px;
  --spacing-base-scale-100: 100px;
  --spacing-base-scale-104: 104px;
// WARNING: This file is generated by a script. Do not edit it manually.
// Generated from: ./lib/tailwind.build.css
// To regenerate: yarn build:tailwind-config

import type { GenericAny } from '@slashfi/slash-base';

const plugin = require('tailwindcss/plugin');

export default {
	theme: {
		extend: {
			backgroundColor: {
				"surface-subtle": "#f9f8f7",
				"surface-neutral": "#ffffff",
				"neutral-subtle-default": "#fcfcfb",
				"neutral-subtle-hover": "#f9f8f7",
				"neutral-subtle-active": "#f9f8f7",
				"neutral-subtle-disabled": "#fcfcfb",
				"neutral-normal-default": "#f9f8f7",
				"neutral-prominent-default": "#f4f3f1",
				"neutral-muted-default": "#e9e7e3",
				"neutral-bold-default": "#958f82",
				"neutral-bold-hover": "#6d685f",
				"neutral-bold-active": "#6d685f",
				"neutral-bold-disabled": "#bdb7aa",
				"neutral-muted-hover": "#e0ddd6",
				"neutral-muted-active": "#d9d6ce",
				"neutral-muted-disabled": "#efeeeb",
				"neutral-prominent-hover": "#efeeeb",
				"neutral-prominent-active": "#efeeeb",
				"neutral-prominent-disabled": "#fcfcfb",
				"neutral-normal-hover": "#f4f3f1",
				"neutral-normal-active": "#f4f3f1",
				"neutral-normal-disabled": "#fcfcfb",
				"neutral-bolder-default": "#6d685f",
				"neutral-boldest-default": "#32312c",
				"neutral-boldest-hover": "#32312c",
				"neutral-boldest-active": "#32312c",
				"neutral-boldest-disabled": "#bdb7aa",
				"neutral-bolder-hover": "#32312c",
				"neutral-bolder-active": "#32312c",
				"neutral-bolder-disabled": "#bdb7aa",
				"interactive-neutral-subtle-default": "#4b3a1c",
				"interactive-neutral-normal-default": "#4b3a1c",
				"interactive-neutral-prominent-default": "#4b3a1c",
				"interactive-neutral-prominent-hover": "#4b3a1c",
				"interactive-neutral-prominent-active": "#4b3a1c",
				"interactive-neutral-prominent-disabled": "#4b3a1c",
				"interactive-neutral-normal-hover": "#4b3a1c",
				"interactive-neutral-normal-active": "#4b3a1c",
				"interactive-neutral-normal-disabled": "#4b3a1c",
				"interactive-neutral-subtle-hover": "#4b3a1c",
				"interactive-neutral-subtle-active": "#4b3a1c",

Para descrever com mais precisão, durante a etapa de compilação, adicionei um script para compilar uma Árvore de Sintaxe Abstrata (AST) a partir dos arquivos CSS do sistema de design. Essa AST forneceu uma representação estruturada das regras CSS e me permitiu analisar facilmente o arquivo para:

  • Filtrar classes CSS não suportadasPor exemplo, estilos de hover e utilitários de ponteiro não funcionam na maioria dos clientes de e-mail.
  • Converter para um sistema de cores compatívelUtilizamos OKLCH para definir nossas cores, mas os clientes de e-mail não conseguem reproduzi-las, por isso elas são convertidas para HEX.
  • Crie o objeto de configuração TS: Isso é compilado em JS e, em seguida, pode ser facilmente importado para qualquer lugar na base de código.

Essa etapa de transpilagem foi uma maneira rápida de implementar uma única fonte de verdade para nosso sistema de design. Se uma cor fosse ajustada, ela seria consistente em todas as nossas mídias — web ou e-mail —, pois seria imediatamente transpilada após a reconstrução. Além disso, outros clientes, como nosso aplicativo móvel, poderiam usar esse objeto transpilado conforme necessário. Com o objeto de configuração gerado, consegui construir um conjunto de componentes React comuns para nossos e-mails que refletiam nossos componentes da web, como Tipografia e Botão componentes, com recursos quase idênticos. Tudo isso ajudou a melhorar a experiência do desenvolvedor, tornando a criação de modelos menos trabalhosa!

Como nota final, também criei modelos de notificação que aproveitam nossa estrutura de registro interna, uma estrutura de dados personalizada que criamos (consulte esta publicação no blog). Resumindo, os registros nos oferecem uma maneira segura de definir modelos, serializá-los e deserializá-los do banco de dados e manter a lógica de negócios definida em conjunto. Por exemplo, o autorização-de-cartão.modelo-de-notificação.tsx fica bem ao lado do código do manipulador de eventos de autorização de cartão, o que é ótimo para ver rapidamente quais eventos têm notificações! Foi ótimo poder usar registros neste projeto – isso realmente ilustrou o compromisso da equipe em escrever um código bom e reutilizável.


O que eu aprendi?

Eu vim pensando que essa seria uma experiência única e estou feliz por ter decidido participar – o Slash me ensinou mais sobre propriedade, design de plataforma e velocidade de engenharia do que qualquer outra função que já desempenhei antes. Algumas das principais lições que aprendi foram:

  • A confiança é inestimável – A Slash não tem reuniões diárias. A Slash não tem sprints semanais nem backlog grooming. Temos uma reunião por semana, e isso é apenas para demonstrar o que construímos para toda a equipe. Isso funciona porque a equipe em geral tem uma comunicação forte e habilidades impressionantes de propriedade. Isso não quer dizer que não haja mentoria – eu tenho reuniões individuais semanais para me desbloquear –, mas, em vez disso, a Slash confia que você fará o seu melhor trabalho e cultiva um ambiente onde você pode se concentrar o máximo possível.
  • A equipe é incrível – Nas minhas outras cooperativas, meus colegas eram profissionais, mas aqui, meus colegas de trabalho eram meu povoÉ evidente que a Slash possui uma excelente cultura, com pessoas divertidas, solidárias e com quem é fácil trabalhar – não houve uma única pessoa com quem eu não gostasse de trabalhar ou que me intimidasse, e eles tratam você como se fosse um funcionário efetivo. Além disso, você pode jogar basquete com eles nos fins de semana.
  • Como estagiário, você crescerá exponencialmente aqui em comparação com qualquer outro lugar. – Em qualquer outra empresa, o estagiário não teria recebido todo o sistema central de notificações como projeto. Os problemas difíceis, que estimulam o crescimento, teriam sido resolvidos ou estariam sendo resolvidos por algum engenheiro sênior. O que torna a Slash única é a combinação de equipes enxutas, problemas críticos reais para os negócios e uma cultura que confia até mesmo aos estagiários trabalhos importantes.

Aqui estão algumas outras lembranças divertidas deste estágio:

  • Comemos cerca de 9 kg de bife em uma semana... obrigado, hack week!
  • Derrotei meu colega estagiário em uma competição de sinuca e, em seguida, fui imediatamente humilhado por um funcionário efetivo.
  • Finalmente consegui levantar um disco!
  • Sou um dos principais contribuintes para a “gula generalizada no escritório” (e, potencialmente relacionado a isso, descobri que adoro o Shake Shack).

Esses quatro meses passaram mais rápido do que eu esperava, e sou grato pela experiência que tive na Slash. Em breve me formarei e, independentemente do que a vida me reservar daqui a um mês, um ano ou uma década, sei que o estágio na Slash me proporcionou o conhecimento e a confiança necessários para ter sucesso, não importa onde eu vá.

Se você estiver interessado em conversar mais, Sinta-se à vontade para entrar em contato comigo no LinkedIn! Fico feliz em responder a quaisquer perguntas que você possa ter.

Read more from us