← Todos os posts
Tecnologia·7 min de leitura·

[0] Criando notas no Obsidian sem estar no Obsidian: visão geral do projeto

Mando mensagem num chat do WhatsApp e ela vira nota formatada no meu Obsidian, com commit direto no GitHub. Tudo numa VPS por ~5 dólares por mês. Como funciona por dentro.

Esse é meu primeiro blog post fora do Substack; vocês provavelmente já perceberam pelo título, ou não né, vai que quem está lendo é um vibe coding. Esse [0] significa que estamos no elemento zero do nosso "array de artigos" sobre esse projeto (se realmente não entendeu até agora, vai aprender o básico meu amigo): vai ser uma série, e os próximos vêm pela frente. Estou construindo tudo em live, então dá pra acompanhar o processo inteiro na playlist completa no YouTube.

Construí um sistema pra resolver uma dor bem específica do meu dia a dia: mandar mensagem num chat do WhatsApp e isso virar uma nota formatada no meu Obsidian, automaticamente, sem eu precisar abrir o computador. Roda numa VPS por ~5 dólares por mês.

Esse post é sobre como ele funciona por dentro, por que cada decisão foi tomada, e o que ainda falta.

O problema

Uso Obsidian como bloco de notas, com o vault inteiro versionado no GitHub. Isso me dá uma coisa importante: clonando o repo em qualquer máquina, eu tenho meu segundo cérebro do lado. Sem app proprietário guardando meus dados, sem sync de terceiros, só git.

Organizo tudo pelo método PARA (Projects, Areas, Resources, Archives, descrito pelo Tiago Forte em fortelabs.com/blog/para). Resumindo bem grosso: tudo o que entra no sistema cai numa pasta inbox/ antes de ser despachado pra um dos quatro lugares definitivos. Pra mim, a inbox/ tem duas subpastas que importam muito:

  • inbox/CONSUMO: notas de coisas externas que eu quero estudar (artigos, podcasts, vídeos, livros, palestras, recomendações).
  • inbox/IDEIAS: pensamentos próprios, sacadas, esboços de projeto.

Como sou dev e também crio conteúdo em paralelo ao trabalho, guardar referências do que consumo é parte do meu fluxo. Quando vou escrever ou gravar algo, eu volto pra essas notas pra puxar contexto, citações, links.

O ritual antigo era esse:

  1. Ler ou ouvir algo interessante.
  2. Abrir o Claude Code dentro do meu vault.
  3. Pedir pra montar a nota no meu padrão (frontmatter, tags, estrutura).
  4. Salvar no Obsidian.

Funciona, mas tem um problema: 80% das vezes eu não estou no computador quando consumo o conteúdo. Tô andando ouvindo podcast, lendo no celular, ou lendo um livro. Quando chegava em casa, já tinha esquecido metade.

Ai eu pensei: e se eu pudesse só mandar mensagem no WhatsApp (Ou qualquer app que tem no meu whats) e isso virasse nota?

A solução

O fluxo de uso ficou assim: mando mensagem (ou várias) num chat do WhatsApp comigo mesmo. Quando termino o pensamento, fecho com uma palavra-chave (ok, salva, fim, vai).

A IA classifica se a mensagem é consumo ou ideia, escolhe o template apropriado, gera a nota no meu padrão, e o commit aparece no GitHub. Não precisei abrir o computador.

Algumas decisões de UX que importaram:

  • Sem prefixo: a primeira versão exigia digitar co ou ide antes de cada mensagem pra dizer o tipo. Era atrito desnecessário, então hoje a IA classifica olhando o conteúdo.
  • Agrupar mensagens em bloco: Você manda "tô lendo esse artigo do akita", depois "achei interessante a parte X", depois "discordo de Y", depois ok. As quatro mensagens viram uma nota só, não quatro notas separadas no vault.

O system design

Em ordem do que acontece quando uma mensagem chega:

  1. Evolution API (cliente WhatsApp self-hosted baseado em Baileys) entrega um webhook na minha API em Fastify quando chega mensagem no chat.
  2. Cada mensagem entra num buffer no Redis. Várias mensagens consecutivas viram um único "bloco" identificado pelo chatId.
  3. Quando chega o terminador (ok, salva, fim, vai), ou quando passam 30 minutos sem nova mensagem (via um sweeper que roda a cada minuto), o bloco fecha.
  4. A API chama o Gemini só pra classificar o bloco como consumo ou ideia. Resposta é um JSON estrito tipo { "kind": "consumo" }.
  5. Com a classificação em mãos, a API escolhe o prompt apropriado (um pra consumo, outro pra ideia) e chama o Gemini de novo, agora pra gerar a nota completa em JSON estruturado: { title, body }.
  6. O Octokit faz o commit do body como arquivo .md direto no repo do vault no GitHub, na pasta certa (inbox/CONSUMO ou inbox/IDEIAS).
  7. A API responde no chat com ✅ Nota criada: <caminho>.

O ponto importante do passo 4 e 5: são duas chamadas separadas ao Gemini. Não é o mesmo modelo fazendo tudo numa tacada. A classificação roda no texto original do bloco; só depois disso a minha API decide qual prompt usar e dispara a segunda chamada. Isso isola responsabilidades: o classificador é um job pequeno e barato, e o gerador recebe um system prompt afinado pro tipo de nota que vai produzir.

Por que o buffer existe

Sem ele, cada mensagem solta viraria uma nota órfã. Com ele, eu posso pensar em voz alta no chat: mandar o link, depois um comentário, depois corrigir o comentário, depois adicionar uma referência, e fechar com ok. Tudo isso é concatenado e mandado pro Gemini de uma vez só.

Diagrama do system design: do WhatsApp ao commit no GitHub

Stack

Em uma linha por componente:

  • Runtime: Node.js 24 (ESM, TypeScript estrito).
  • API: Fastify + zod pra validação de payloads.
  • Redis: ioredis. Cuida do buffer de bloco, idempotência por mensagem, idempotência por bloco, dedupe outbound.
  • IA: Google Gemini via API do AI Studio. Mesmo modelo pro classificador e pro gerador, prompts diferentes.
  • GitHub: Octokit, commit direto via API (sem clone local).
  • Logger: pino estruturado, com redação de segredos.
  • Testes: vitest, foco em unidades do core (parser, terminator, slugify, blockManager, processBlock).
  • Cliente WhatsApp: Evolution API self-hosted (precisa de Postgres pra estado dela e Redis pra cache; reaproveito o mesmo Redis da minha aplicação).
  • Lint/format/hooks: Biome, Lefthook e commitlint, com Conventional Commits validados no commit.

Onde roda

Tudo num único docker-compose em uma VPS Hetzner barata, custando ~5 dólares por mês. Os serviços rodando são:

  • whatsapp-to-obsidian: a minha API.
  • evolution-api: cliente WhatsApp.
  • redis: compartilhado entre Evolution e minha API (databases separados via prefixo de chave).
  • postgres: usado só pela Evolution API.

Pipeline de deploy: push na master dispara um workflow no GitHub Actions que roda lint, typecheck, testes, builda a imagem Docker, publica em ghcr.io, faz SSH na VPS e roda docker compose pull && up -d. O webhook é protegido por token aleatório no path e pelo isolamento de rede Docker (a API não é exposta externamente; só a Evolution fala com ela na rede interna do compose). Dependabot cuida de atualizar npm, GitHub Actions e imagens Docker.

Próximos passos do roadmap

  1. Suporte a áudio.
  2. Enriquecimento de URLs via Jina Reader.
  3. Transcrição automática de vídeos do YouTube.
  4. Suporte a imagem (OCR).
  5. Melhorar qualidade das notas geradas.

Fechamento

A reflexão que fica é uma coisa que já ouvi várias vezes mas só agora vivi: os projetos mais úteis pra você mesmo são os pequenos, focados numa dor sua, que você usa todo dia. Esse aqui é a primeira versão útil; vai continuar evoluindo conforme eu mesmo for usando e descobrindo os limites.

Se você também usa Obsidian, ou tem essa dor de captura longe do computador, me conta como você resolveu. Tô curioso pra ver outras abordagens.

Gostou deste artigo?

Assine no Substack para receber os próximos artigos direto no seu email.

Assinar no Substack