Automatizando newsletter para ajudar a melhorar o meu vocabulário em inglês
TLDR; Famoso senta que la vem história, mas que histórica bacana que venho contar hoje. Durante o carnaval tive uma idéia bem bacana para ajudar desenvolvedores a aumentar o seu vocabulário em inglês.
Com isso construi uma newsletter gratuita e automática que envia uma palavra de estudo por dia visando ajudar a galera nos seguintes pontos:
- Tradução;
- Definição;
- Exemplo em entrevistas de emprego (Como recrutadores podem utilizar essas palavras em entrevistas);
- Origem da palavra;
- E qual a importancia dela;
- Além de trazer uma visão das vagas nas ultimas 24 horas fora do país.
Com isso nasceu o uma espécie de Duolingo em formato de newsletter só que para programadores.
E po como acredito que tem uma galera aqui que pode aprender com esse tipo de construção decidi trazer alguns pontos sobre o desenvolvimento.
Gerando o conteúdo
Para gerar o conteúdo eu vou ser bem sincero, entrei no gepeto pedi 50 termos técnicos em inglês sobre o assunto tecnologia, fiz isso pelo chat mesmo sem usar nenhuma API nem nada. Ganhei 50 dias de conteúdo e fui embora sem olhar para trás kkk
Peguei essa base gerada que na solicitação pedi para que fosse em json, copiei e montei uma função que integrasse isso tudo dentro de um Cloud Firestore do Google. Optei por um banco noSQL por causa da sua estrutura mesmo, não tinha a necessidade de relacionamento e no final são documentos unicos que falam sobre uma palavra por vez.
PS. Minha dica é sempre definir qual é o output da solicitação, quero que voce faça isso e que a saída seja em CSV, ou JSON, ou JSONL
Aproveitei para montar nessa inserção uma slug para cada palavra, para caso precisasse carregar isso em um site ter as urls amigáveis. Usei esse pacote do npm slug
para tratar esse processo. Poderia ter pedido isso para o proprio prompt, mas acabei esquecendo.
Ahh sobre o stack
Acabei utilizando o Firebase para o back-end em uma estrutura serveless pq o projeto é bem simples. Em uma das funções realizo a geração das informações: Tradução, Definição e etc. Na outra realizo o disparo do e-mail, simples e prático. Tudo usando NodeJs, Cloud Functions for Firebase e o Cloud Firestore para o banco no SQL.
Gerando as informações de cada palavra
Montei usando a OPENAI mesmo só que dessa vez usando o GPT-4 como modelo, alguém tinha me falado para usar o https://maritaca.ai/ eu em breve vou migrar para ele. Dentro dessa estrutura meu prompt ficou dessa mais ou menos assim:
Generate a detailed description of a this word ${word} related to programming that might be used in a job interview context. Include its PT-BR translation, a definitions in PT-BR, example sentences in EN-US that resemble common job interview questions or answers, the word's origin, and a brief explanation of its relevance. Structure the response as a JSON object with properties: word, translation, definitions, example_sentences (tailored to job interview scenarios),
word_origin, and why_this_word.
The content should be educational
and practical for job applicants.
Example format:
{
"word": "[ExampleWord em EN-US]",
"translation": "[PalavraExemplo em PT-BR]",
"phonetic_pronunciation": "[transcrição fonética detalhada da palavra usando o Alfabeto Fonético Internacional (AFI). Inclua a separação silábica e indique a ênfase tônica das palavras]"
"definitions": [
{
"class": "[classe da palavra em PT-BR]",
"options": ["Definição 1.", "Definição 2."]
},
{
"class": "[classe da palavra]",
"options": ["Definição 1."]
}
],
"example_sentences":
["Sentence 1 using the word in job interview in USA.",
"Sentence 2 using the word job interview in USA",
"Sentence 3 using the word job interview in USA"],
"word_origin": "[Origem da palavra em PT-BR]",
"why_this_word":
"[Um fato interessante ou a razao do por que programadores precisam saber essa palavra em PT-BR]"
}
`
O resultado eu salvo no banco do Firestore e foi :)
Gerando o HTML do email e escolhendo o sender
Para o HTML do e-mail acabei usando o reactMail para efetuar o disparo de forma bem simples, pego a ultima palavra que foi adicionada informação no banco de dados e monto o meu template. Já estava a um tempo querendo testar o reactMail e essa foi uma puta oportunidade excelente iniciativa by the way. Vou deixar o código do template sei la que, vai que ajuda alguém:
//newsletter.jsx
import {
Body,
Container,
Column,
Head,
Heading,
Hr,
Html,
Row,
Section,
Text,
Link,
Img
} from "@react-email/components";
import { Tailwind } from "@react-email/tailwind";
import * as React from "react";
export const DevLingoNewsletter = ({
title,
word,
definitions,
why_this_word,
word_origin,
example_sentences,
jobs,
userId,
translation
}) => {
return (
<Html>
<Tailwind>
<Head />
<Body className="bg-gray-100 my-auto mx-auto font-sans px-2">
<Container className="my-[40px] mx-auto p-[20px] max-w-[600px]">
<Heading className=" font-normal text-center p-0 my-[30px] mx-0">
<Link className="text-black" href={"https://devlingo.com.br?utm_source=newsleter&utm_medium=email"}>{`</lingo>`}</Link>
</Heading>
<Text className="text-[32px] text-center text-white text-center bg-gray-900 py-24 m-0 leading-[36px]">
{`🇺🇸 "${word}"`} <br/>
<em className="text-[24px] mt-[6px]">{`🇧🇷 ${translation}`}</em>
</Text>
<Section className="mt-[0px] bg-white px-10 py-5 hidden sm:block rounded-md rounded-tl-none rounded-tr-none">
{definitions.map((definition, rowIndex) => (
<Row key={`definition-${rowIndex}`}>
<Column valign="top">
<strong className="text-[18px] text-gray-400">{definition.class}</strong>
</Column>
<Column valign="right">
<ol className="mt-0 pt-0">
{definition.options.map((option, index) => (
<li key={`option-${rowIndex}-${index}`} className="text-[16px] mb-4">{option}</li>
))}
</ol>
</Column>
</Row>
))}
</Section>
<Section className="mt-[0px] bg-white px-6 sm:px-10 py-5 block sm:hidden rounded-md">
{definitions.map((definition, rowIndex) => (
<Row key={`definition-${rowIndex}`}>
<Column valign="right">
<strong className="text-[18px] text-gray-400">{definition.class}</strong>
<ol className="mt-4 pt-0 pl-4">
{definition.options.map((option, index) => (
<li key={`option-${rowIndex}-${index}`} className="text-[16px] mb-4">{option}</li>
))}
</ol>
</Column>
</Row>
))}
</Section>
<Section className="mt-[32px] mb-[32px] bg-white px-10 py-5 rounded-md">
<Row>
<Column>
<Text className="text-[24px]">
{`Exemplos em entrevistas de emprego`}
</Text>
</Column>
</Row>
<Row>
<Column>
<ol>
{example_sentences.map((sentence, index) => (
<li key={`sentence-${index}`} className="text-[16px] mb-4">{sentence}</li>
))}
</ol>
</Column>
</Row>
</Section>
<Section className="mt-[32px] mb-[32px] bg-white px-10 py-5 rounded-md">
<Row>
<Column>
<Text className="text-[24px]">
{`Qual é a origem?`}
</Text>
</Column>
</Row>
<Row>
<Column>
<Text className="text-[18px]">
{word_origin}
</Text>
</Column>
</Row>
</Section>
<Section className="mt-[32px] mb-[32px] bg-white px-10 py-5 rounded-md">
<Row>
<Column>
<Text className="text-[24px]">
{`Por que devemos aprender?`}
</Text>
</Column>
</Row>
<Row>
<Column>
<Text className="text-[18px]">
{why_this_word}
</Text>
</Column>
</Row>
</Section>
<Section className="mt-[32px] mb-[32px] bg-white px-10 py-5 rounded-md">
<Row>
<Column>
<Text className="text-[24px]">
{`Vagas das últimas 24H ⏱️`}
</Text>
</Column>
</Row>
<Row>
<Column>
{jobs.map(job => (
<Link href={job.link}>
<Text className="text-[18px]">
{job.title}
</Text>
</Link>
))}
</Column>
</Row>
</Section>
<Section className="mt-[32px] mb-[32px] bg-white px-10 py-5 rounded-md">
<Row>
<Column>
<Text className="text-[24px]">
{`Curtiu? Agora manda pra um amigo! `}
</Text>
</Column>
</Row>
<Row>
<Column>
<Img src={`https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExNThvNHExNHJmaXg5N3VxNDdhcnpqMDdxY2xvbDZsZHZuN3l2M3h3dSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/GrkrL1cGVv0FW/giphy.gif`} alt="The it crowd" />
</Column>
</Row>
<Row>
<Column>
<Text className="text-[16px]">
{`O projeto </lingo> nasceu de um sonho individual de fazer a diferença na vida dos programadores, oferecendo ferramentas e recursos de qualidade sem custo. Nosso objetivo é crescer e continuar a oferecer nosso serviço gratuitamente para você, o consumidor final. Mas, para isso, precisamos da sua ajuda!`}
</Text>
</Column>
</Row>
<Row>
<Column>
<h3> Como você contribui para o nosso crescimento?</h3>
</Column>
</Row>
<Row>
<Column>
<ul className="text-[16px]">
<li className="mb-2">Divulgue para os amigos: Se você acha que o que fazemos aqui pode ajudar mais pessoas, compartilhe o {`</lingo>`} pelo {" "}
<Link href={`https://api.whatsapp.com/send?text=${encodeURI(`Pessoal da uma olhada nessa newsletter que eu encontrei para ajudar a galera que está querendo aprender inglês, é só se inscrever em https://devlingo.com.br`)}`}>
Whatsapp
</Link>, <Link href={`https://twitter.com/intent/post?text=${encodeURI("Pessoal da uma olhada nessa newsletter que eu encontrei para ajudar a galera que está querendo aprender inglês, é só se inscrever")}&url=${encodeURI("https://devlingo.com.br")}`}>Twitter</Link> {" "} ou {" "} <Link href=""> Linkedin</Link>. Um pequeno gesto seu pode significar um grande passo para nós.
</li>
<li className="mb-2">Interaja com anúncios: Sabemos que anúncios podem ser um incômodo, mas, se eventualmente aparecerem por aqui, clicar neles é uma maneira simples e eficaz de nos apoiar sem custo algum para você.</li>
<li className="mb-2">Mencione-nos: Se encontrar uma oportunidade profissional por meio do {"</lingo>"}, mencione-nos ao se candidatar. Isso ajuda a fortalecer nossa rede e a mostrar o valor da nossa comunidade.</li>
</ul>
</Column>
</Row>
<Row>
<Column>
<p className="text-[16px]">Juntos, podemos fazer do {`</lingo>`} uma ferramenta ainda mais poderosa para programadores em todo o mundo. </p>
<p className="text-[16px]">Obrigado por fazer parte desta jornada conosco!</p>
</Column>
</Row>
</Section>
<Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
<Text className="text-[#666666] text-center text-[12px] leading-[24px]">
Caso não queira receber mais e-mails como esse basta clicar nesse <Link href={`https://devlingo.com.br/api/rmSub?id=${userId}`}>{` link `}</Link>
vamos ficar muito triste, espero que um dia você volte. Fique a vontade para mandar o {`</lingo>`} <Link href={`https://devlingo.com.br`}>{` para um amigo `}</Link>.
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
};
DevLingoNewsletter.PreviewProps = {
title: '</lingo>: Palavra do dia "Algorithm"',
word: "Algorithm",
translation: "Algoritmo",
preheader: "👀 Aprenda uma nova palavra todo dia.",
definitions: [{
class: "Substantivo",
options: ["Um conjunto de instruções ou regras definidas e organizadas que permitem a realização de uma tarefa ou problema específico.", "Um procedimento passo a passo usado em programação para resolver um problema ou atingir um objetivo específico."],
}],
example_sentences: ["Can you describe an algorithm you've developed that you're particularly proud of?", "What is your approach to developing efficient algorithms?", "Tell me about a time when you optimized an algorithm to improve its performance."],
why_this_word: "A compreensão e a capacidade de desenvolver algoritmos eficientes são habilidades fundamentais para qualquer programador ou cientista da computação. Durante entrevistas de emprego em programação, é comum que os entrevistadores peçam aos candidatos para discutir ou demonstrar suas habilidades de algoritmo.",
word_origin: "A palavra 'Algoritmo' é derivada da latinização do nome de Muhammad ibn Musa al-Khwarizmi, um matemático persa, astrônomo e geógrafo, que foi um dos primeiros a introduzir os algoritmos no Ocidente.",
jobs: [{
title: "Developer analytics - Hybrid - Canadá",
link: "https://metricasboss.com.br"
}],
userId: "1235455"
};
export default DevLingoNewsletter;
Ahh sobre o sender de e-mail decidi usar o Resend mesmo, nenhum dessas ferramentas de newsletter prestam quando o assunto é fazer algo 100% automático, o Resend é muito bom, precisa só adicionar dentro da API dele a criação de broadcast #ficaadica
Com isso após mandar para alguns amigos BOOM 100 usuários em sei lá, 3 dias de projeto. Sempre quis fazer alguma coisa para devolver para comunidade de desenvolvimento, quem sabe esse não seja um projeto bacana.
Para quem quiser se inscrever só colar no link que inclusive foi feito usando nextjs14
.