diff --git a/app/data/Newsletter.ts b/app/data/Newsletter.ts
index 8e16d2b..ad1fc58 100644
--- a/app/data/Newsletter.ts
+++ b/app/data/Newsletter.ts
@@ -1,16 +1,17 @@
-import type Story from "./Story";
-
export type Newsletter = {
- createdAt: string;
- publishedAt: string;
- id: string;
- issueNumber: number;
- messageBody: string;
- preview: boolean;
+ _id: string;
+ _createdAt: string;
+ _updatedAt: string;
subject: string;
- updatedAt: string;
+ preview: string;
+ body: string;
+ sendAt: string;
+ shouldSend: boolean;
sendGridId: string;
sendGridDesignId: string;
- sendAt: string;
- story: Story;
+ author: {
+ bio: string;
+ name: string;
+ image: string;
+ };
};
diff --git a/app/data/sanity.ts b/app/data/sanity.ts
new file mode 100644
index 0000000..7384ec4
--- /dev/null
+++ b/app/data/sanity.ts
@@ -0,0 +1,15 @@
+export const sanityClient = {
+ async mutate(mutations: object[]) {
+ return await fetch(
+ `https://${process.env.SANITY_PROJECT_ID}.api.sanity.io/v2021-06-07/data/mutate/production`,
+ {
+ method: "post",
+ headers: {
+ "Content-type": "application/json",
+ Authorization: `Bearer ${process.env.SANITY_TOKEN}`
+ },
+ body: JSON.stringify({ mutations })
+ }
+ );
+ }
+};
diff --git a/app/emails/ConfirmSubscription.tsx b/app/emails/ConfirmSubscription.tsx
index b9e2dba..afe8824 100644
--- a/app/emails/ConfirmSubscription.tsx
+++ b/app/emails/ConfirmSubscription.tsx
@@ -24,7 +24,10 @@ export function ConfirmSubscription({ recipient }: { recipient: string }) {
Best,
-
+
Drew Lyton
diff --git a/app/emails/NewPostNewsletter.tsx b/app/emails/NewPostNewsletter.tsx
index 83784ea..c9eb3a3 100644
--- a/app/emails/NewPostNewsletter.tsx
+++ b/app/emails/NewPostNewsletter.tsx
@@ -2,47 +2,29 @@ import {
Button,
Column,
Img,
+ Preview,
Row,
Section,
Text
} from "@react-email/components";
import { getMDXComponent } from "mdx-bundler/client";
-import { PropsWithChildren } from "react";
+import type { PropsWithChildren } from "react";
+import type { Newsletter } from "~/data/Newsletter";
import type Story from "~/data/Story";
-type NewsletterEmailProps = {
- messageBody?: string[];
- story?: Pick<
- Story,
- "title" | "description" | "featuredImage" | "slug" | "author"
- >;
-};
+type NewsletterEmailProps = Pick;
export function NewPostNewsletter({
- messageBody = [
- "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
- "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
- ],
- story = {
- title: "Latest post title",
- description: "Latest post description",
- featuredImage: {
- url: "https://media.graphassets.com/Eq1aApzqQ2iRqhxKjEBb"
- },
- slug: "test-slug",
- author: {
- bio: "Test bio",
- name: "Drew Lyton",
- picture: { url: "https://www.drewis.cool/headshot.png" }
- }
- }
+ body,
+ author,
+ preview
}: NewsletterEmailProps) {
- const MDXComponentAboveFold = getMDXComponent(messageBody[0]);
- const MDXComponentBelowFold = getMDXComponent(messageBody[1]);
+ const MDXComponentAboveFold = getMDXComponent(body);
const MDXComponents = { p: MDXText, blockquote: MDXBlockQuote };
return (
<>
+ {preview}
@@ -51,33 +33,17 @@ export function NewPostNewsletter({
-
-
Until next time,
-
+
-
- {story.author.name}
-
- {story.author.bio}
+ {author.name}
+ {author.bio}
>
@@ -95,3 +61,24 @@ const MDXBlockQuote: React.FC = ({ children }) => {
);
};
+
+const MDXStory: React.FC<{ story: Story }> = ({ story }) => {
+ return (
+
+ );
+};
diff --git a/app/routes/newsletter/$issueNumber/index.tsx b/app/routes/newsletter/$issueNumber/index.tsx
index 223b2e5..3892517 100644
--- a/app/routes/newsletter/$issueNumber/index.tsx
+++ b/app/routes/newsletter/$issueNumber/index.tsx
@@ -1,13 +1,14 @@
import { render } from "@react-email/render";
import { LoaderArgs, json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
+import { bundleMDX } from "mdx-bundler";
import { useEffect } from "react";
+import remarkGfm from "remark-gfm";
import GetNewsletter from "~/data/GetNewsletter";
import { Newsletter } from "~/data/Newsletter";
import { client } from "~/data/client";
import { EmailLayout } from "~/emails/EmailLayout";
import { NewPostNewsletter } from "~/emails/NewPostNewsletter";
-import { getMessageBodyMarkdown } from "~/helpers/getMessageBodyMarkdown";
import { useTheme } from "~/helpers/useTheme";
export async function loader({ params }: LoaderArgs) {
@@ -24,16 +25,23 @@ export async function loader({ params }: LoaderArgs) {
);
if (newsletter.preview) throw new Response("Not found", { status: 404 });
- const [messageAboveLink, messageBelowLink] = await getMessageBodyMarkdown(
- newsletter.messageBody
- );
+ const { code: messageBody } = await bundleMDX({
+ source: newsletter.body,
+ mdxOptions(options, frontmatter) {
+ // this is the recommended way to add custom remark/rehype plugins:
+ // The syntax might look weird, but it protects you in case we add/remove
+ // plugins in the future.
+ options.remarkPlugins = [...(options.remarkPlugins ?? []), remarkGfm];
+
+ return options;
+ }
+ });
- return json({ messageAboveLink, messageBelowLink, newsletter });
+ return json({ messageBody, newsletter });
}
export default function ViewNewsletter() {
- const { newsletter, messageAboveLink, messageBelowLink } =
- useLoaderData();
+ const { newsletter, messageBody } = useLoaderData();
const { theme, toggleTheme } = useTheme();
@@ -47,10 +55,7 @@ export default function ViewNewsletter() {
dangerouslySetInnerHTML={{
__html: render(
-
+
)
}}
diff --git a/app/routes/newsletter/webhook.tsx b/app/routes/newsletter/webhook.tsx
index 29e5e27..2c0729d 100644
--- a/app/routes/newsletter/webhook.tsx
+++ b/app/routes/newsletter/webhook.tsx
@@ -1,27 +1,23 @@
import { render } from "@react-email/render";
import { ActionArgs, json } from "@remix-run/node";
-import { Newsletter } from "~/data/Newsletter";
-import UpdateSendGridId from "~/data/UpdateSendGridId";
-import { client } from "~/data/client";
+import { bundleMDX } from "mdx-bundler";
+import remarkGfm from "remark-gfm";
+import type { Newsletter } from "~/data/Newsletter";
+import { sanityClient } from "~/data/sanity";
import { sendgridClient } from "~/data/sendgrid";
import { EmailLayout } from "~/emails/EmailLayout";
import { NewPostNewsletter } from "~/emails/NewPostNewsletter";
-import { getMessageBodyMarkdown } from "~/helpers/getMessageBodyMarkdown";
-
-type GraphCMSWebhookBody = {
- data?: Newsletter;
-};
export async function action(args: ActionArgs) {
// Get Newsletter body
- const { data: newsletter } =
- (await args.request.json()) as GraphCMSWebhookBody;
+ const newsletter = (await args.request.json()) as Newsletter;
+ console.log({ newsletter });
// Throw if doesn't exist
- if (!newsletter) return new Response("No request body", { status: 401 });
- const designHTML = generateHTMLEmail(newsletter);
+ if (!newsletter) return new Response("No request body", { status: 200 });
+ const designHTML = await generateHTMLEmail(newsletter);
// Throw if newsletter is in preview mode
- if (newsletter.preview)
+ if (!newsletter.shouldSend)
return new Response(
"Can't send a nesletter that's still in preview mode.",
{
@@ -37,7 +33,7 @@ export async function action(args: ActionArgs) {
url: "/v3/designs",
method: "POST",
body: {
- name: `Issue #${newsletter.issueNumber}`,
+ name: `Issue #${newsletter._id}`,
html_content: designHTML,
editor: "code",
subject: newsletter.subject
@@ -46,11 +42,20 @@ export async function action(args: ActionArgs) {
if (designResponse.statusCode >= 300)
return new Response("Couldn't create design in sendgrid.", {
- status: 501
+ status: 200
});
// Update the Newsletter with the design ID
- client.request(UpdateSendGridId, { sendGridDesignId: design.id });
+ await sanityClient.mutate([
+ {
+ patch: {
+ id: newsletter._id,
+ set: {
+ sendGridDesignId: design.id
+ }
+ }
+ }
+ ]);
newsletter.sendGridDesignId = design.id;
}
@@ -60,7 +65,7 @@ export async function action(args: ActionArgs) {
url: "/v3/marketing/singlesends",
method: "POST",
body: {
- name: `Issue #${newsletter.issueNumber}`,
+ name: `Issue #${newsletter._id}`,
// Send 5 minutes from now in case need to cancel it
send_at: newsletter.sendAt,
send_to: {
@@ -79,15 +84,24 @@ export async function action(args: ActionArgs) {
if (sendResponse.statusCode >= 300)
return new Response("Couldn't create single send in sendgrid.", {
- status: 501
+ status: 200
});
- client.request(UpdateSendGridId, { sendGridId: singleSend.id });
+ await sanityClient.mutate([
+ {
+ patch: {
+ id: newsletter._id,
+ set: {
+ sendGridId: singleSend.id
+ }
+ }
+ }
+ ]);
} else {
const [sendResponse, singleSend] = await sendgridClient.request({
url: `/v3/marketing/singlesends/${newsletter.sendGridId}`,
method: "PATCH",
body: {
- name: `Issue #${newsletter.issueNumber}`,
+ name: `Issue #${newsletter._id}`,
// Send 5 minutes from now in case need to cancel it
send_at: newsletter.sendAt,
send_to: {
@@ -106,7 +120,7 @@ export async function action(args: ActionArgs) {
if (sendResponse.statusCode >= 300)
return new Response("Couldn't update single send in sendgrid.", {
- status: 501
+ status: 200
});
}
@@ -114,18 +128,19 @@ export async function action(args: ActionArgs) {
}
export async function generateHTMLEmail(newsletter: Newsletter) {
- const [messageAboveLink, messageBelowLink] = await getMessageBodyMarkdown(
- newsletter.messageBody
- );
+ const { code: messageBody } = await bundleMDX({
+ source: newsletter.body,
+ mdxOptions(options, frontmatter) {
+ options.remarkPlugins = [...(options.remarkPlugins ?? []), remarkGfm];
+ return options;
+ }
+ });
// Render HTML of latest newsletter
// use {{email}} for the recipient field
const html = render(
-
+
);
diff --git a/env.d.ts b/env.d.ts
index 9554754..1b214e5 100644
--- a/env.d.ts
+++ b/env.d.ts
@@ -7,5 +7,7 @@ declare namespace NodeJS {
SENDGRID_TEST_LIST: string;
CONVERTKIT_API_SECRET: string;
CONVERTKIT_STORY_FORM_ID: string;
+ SANITY_TOKEN: string;
+ SANITY_PROJECT_ID: string;
}
}