Skip to content

Commit

Permalink
fix: better email
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamesb committed Mar 2, 2024
1 parent 924d125 commit d198a01
Show file tree
Hide file tree
Showing 7 changed files with 2,824 additions and 180 deletions.
3 changes: 3 additions & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dependencies": {
"@prisma/client": "5.8.0",
"@quixo3/prisma-session-store": "^3.1.13",
"@react-email/components": "^0.0.15",
"aws-sdk": "^2.1565.0",
"cookie-parser": "^1.4.6",
"cookie-session": "^2.0.0",
Expand All @@ -19,6 +20,7 @@
"nodemon": "^3.0.2",
"passport": "^0.7.0",
"passport-twitter": "^1.0.4",
"react-email": "^2.1.0",
"resend": "^3.1.0",
"uuid": "^9.0.1",
"zod": "^3.22.4"
Expand All @@ -35,6 +37,7 @@
},
"scripts": {
"download-secrets": "npx tsx scripts/downloadSecrets.ts",
"email": "email dev -d src/lib/email",
"dev": "nodemon -x 'tsc -b && ts-node src/main.ts'",
"start": "ts-node --transpile-only src/main.ts",
"build": "yarn && npx prisma generate && npx prisma migrate deploy",
Expand Down
147 changes: 147 additions & 0 deletions packages/server/src/lib/email/RecommendationsEmail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { Button } from "@react-email/button";
import {
Body,
Container,
Head,
Heading,
Html,
Preview,
Row,
Section,
Text,
Tailwind,
Img,
Link,
Column,
} from "@react-email/components";
import React from "react";
import { AddRecommendationInput } from "../addRecomendations";

interface RecommendationsEmailProps {
input: AddRecommendationInput;
}

const defaults: RecommendationsEmailProps = {
input: {
username: "experilearning",
clips: {
"How to make a website": {
"How to make a website from scratch": [
{
title: "How to make a website from scratch",
question: "How to make a website from scratch",
text: "This is a video about how to make a website from scratch",
articleTitle: "How to make a website from scratch",
type: "article",
articleUrl: "https://example.com",
},
],
},
},
},
};

export default function RecommendationsEmail({
input = defaults.input,
}: RecommendationsEmailProps) {
return (
<Html>
<Head />
<Preview>Open Recommender Recommendations</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: "#2250f4",
offwhite: "#fafbfb",
},
spacing: {
0: "0px",
10: "10px",
20: "20px",
45: "45px",
},
},
},
}}
>
<Body className="font-sans text-base bg-offwhite">
<Img
src={`https://open-recommender.com/logo.webp`}
width="184"
alt="Open Recommender"
className="mx-auto my-20"
/>
<Container className="bg-white px-45">
<Heading className="my-0 leading-8 text-center">
New Recommendations
</Heading>
<Section>
<Row>
<Text className="text-base text-center">
View your latest recommendations below or in your feed.
</Text>
</Row>
</Section>
<Section className="text-center">
<Button
href={`https://open-recommender.com/#/${input.username}/feed`}
className="bg-brand text-white rounded-lg py-3 px-[18px]"
>
Go to your feed
</Button>
</Section>
{input.username === "experilearning" && (
<Section>
<pre>{JSON.stringify(input, null, 2)}</pre>
</Section>
)}

<Section>
{
// Loop through the recommendations and create a section for each query
Object.entries(input.clips).map(([query, clusters], i) => {
return (
<Section key={i}>
<Heading as="h2">{query}</Heading>
{
// Loop through the clusters and create a section for each question
Object.entries(clusters).map(([question, clips], i) => {
return (
<React.Fragment key={i}>
<Heading as="h4">{question}</Heading>
{clips.map((clip) => {
return (
<Row>
<Text>{clip.text}</Text>
</Row>
);
})}
</React.Fragment>
);
})
}
</Section>
);
})
}
</Section>
<Container className="mt-20">
<Section>
<Row>
<Column className="px-20 text-right">
<Link>Give Feedback</Link>
</Column>
<Column className="text-left">
<Link>Manage Preferences</Link>
</Column>
</Row>
</Section>
</Container>
</Container>
</Body>
</Tailwind>
</Html>
);
}
12 changes: 0 additions & 12 deletions packages/server/src/lib/sendEmail.ts

This file was deleted.

25 changes: 25 additions & 0 deletions packages/server/src/lib/sendEmail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { User } from "@prisma/client";
import { Resend } from "resend";
import { AddRecommendationInput } from "./addRecomendations";
import RecommendationsEmail from "./email/RecommendationsEmail";
import React from "react";

const subjectLines = [
"Fresh recommendations",
"New nuggets",
"New recommendations",
];

export async function sendRecommendationsEmail(
toUser: User,
input: AddRecommendationInput
) {
const resend = new Resend(process.env.RESEND_API_KEY);
const subject = subjectLines[Math.floor(Math.random() * subjectLines.length)];
await resend.emails.send({
from: process.env.NO_REPLY_EMAIL!,
to: toUser.email,
subject,
react: <RecommendationsEmail input={input} />,
});
}
38 changes: 2 additions & 36 deletions packages/server/src/tasks/twitterPipeline.saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { brainstormQuestions } from "cli/src/recommender/prompts/brainstormSubQu
import { pAll } from "cli/src/utils/pAll";
import {
MetaphorArticleResult,
VideoResultWithTranscript,
VideoResultWithTranscriptFile,
searchNonYouTube,
searchYouTube,
Expand All @@ -32,11 +31,10 @@ import { nearestSubstring } from "cli/src/metaphor/nearestSubstring";
import { findStartOfAnswerYouTube } from "cli/src/recommender/prompts/findStartOfAnswerYouTube/findStartOfAnswerYouTube";
import { uniqBy } from "remeda";
import { addRecommendations } from "../lib/addRecomendations";
import { sendEmail } from "../lib/sendEmail";
import { sendRecommendationsEmail } from "../lib/sendEmail";
import { TranscriptClipWithScore } from "shared/src/manual/TranscriptClip";
import { ArticleSnippetWithScore } from "shared/src/manual/ArticleSnippet";
import { chunksToClips } from "cli/src/recommender/chunksToClips";
import { readFileSync } from "fs";

type QueryWithSearchResultWithTranscript = {
searchResults: (VideoResultWithTranscriptFile | MetaphorArticleResult)[];
Expand Down Expand Up @@ -568,39 +566,7 @@ export const twitterPipeline = new Saga(
);
return;
} else {
const formatEmail = () => {
let str = ``;
for (const [query, clusters] of Object.entries(finalData.clips)) {
str += `<h3>${query}</h3>`;
for (const [question, clips] of Object.entries(clusters)) {
str += `<h4>${question}</h4>`;
str += `<ul>`;
for (const clip of clips) {
str += `<li><a href="${
clip.type === "article" ? clip.articleUrl : clip.videoUrl
}">${clip.title}</a></li>`;
}
str += `</ul>`;
}
}
return str;
};

await sendEmail(
user,
"Your latest recommendations are ready!",
`
Hi ${user.name},
Your latest recommendations are ready. You can also view them <a href="https://open-recommender.com/#/user/experilearning/feed">here</a>.
${formatEmail()}
Best,
<a href="https://twitter.com/experilearning">James</a>
`.trim()
);
await sendRecommendationsEmail(user, finalData);
helpers.logInfo("Email sent");
}
},
Expand Down
3 changes: 2 additions & 1 deletion packages/server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"strict": true /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
"declaration": true,
"declarationMap": true
"declarationMap": true,
"jsx": "react"
}
}
Loading

0 comments on commit d198a01

Please sign in to comment.