How To Build Your Own Newsletter App? р.2

Emil Pearce - May 16 - - Dev Community

Sending newsletters with Novu and React Email

We’ll leverage Novu’s code-first workflow (Echo) approach in creating notification workflows within our codebase. Novu empowers you with the ability to integrate email, SMS, and chat template and content generators, such as React Emailand MJML into your app via code.

Within this section, you'll grasp the art of crafting notification workflows using Novu Echo, designing email templates with React Email, and seamlessly sending emails via Novu.

First, you need to create an account on Novu and set up a primary email provider. We'll use Resend for this tutorial.

After creating an account on Novu, create a Resend account, and select API Keys from the sidebar menu on your dashboard to create one.

Resend API key

Next, return to your Novu dashboard, select Integrations Store from the sidebar menu, and add Resend as an email provider. You'll need to paste your Resend API key and email address into the required fields.

Novu

Finally, select Settings from the sidebar menu and copy your Novu API key into a .env.local file as shown below.

# For Novu SDK
NOVU_API_KEY=<YOUR_NOVU_API_KEY>

# For Echo Client
NEXT_PUBLIC_NOVU_API_KEY=<YOUR_NOVU_API_KEY>
Enter fullscreen mode Exit fullscreen mode

Novu API

The API route for creating newsletters

Navigate to the components/Newsletters.tsx file, which contains the form that enables Admin users to send newsletters to subscribers. Copy the functions below into this file.

const [subscribers, setSubscribers] = useState<SubscribersData[]>([]);

//👇🏻 fetch the subscribers' emails from Firebase
const fetchSubscribers = async () => {
    try {
        const querySnapshot = await getDocs(collection(db, "subscribers"));
        const emails: string[] = [];
        const subs: any = [];
        querySnapshot.forEach((doc) => {
            const dt = doc.data();
            emails.push(dt.email);
            subs.push(dt);
        });
        setSubscribers(subs);
    } catch (e) {
        console.error("Error fetching subscribers", e);
    }
};

//👇🏻 gets the list of subscribers on page load
useEffect(() => {
    fetchSubscribers();
}, []);

//👇🏻 send form data to the backend API route
const emailNewsletter = async () => {
    try {
        const request = await fetch("/api/send", {
            method: "POST",
            body: JSON.stringify({ subject, message, subscribers }),
        });
        const response = await request.json();
        if (response.success) {
            console.log({ response });
        }
    } catch (err) {
        console.error(err);
    }
};
Enter fullscreen mode Exit fullscreen mode

The provided code snippet retrieves the list of subscribers from Firebase and sends them, along with the email's message and subject, to the /api/send API route, where the emails are forwarded to the subscribers via Novu.

The fetchSubscribers function retrieves the subscribers from Firebase, while the emailNewsletter function sends a POST request containing the subscribers' data, email subject, and content to the api/send Next.js API route.

To proceed, let's set up the required API routes. Create an api/send folder within the Next.js app folder.

cd app
mkdir api && cd api
mkdir send
Enter fullscreen mode Exit fullscreen mode

Create a route.ts file within the send folder that accepts the form data.

import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
    const { subject, message, subscribers } = await req.json();

    return NextResponse.json(
        {
            message: "Data received!",
            data: {subject, message: subscribers}
            success: true,
        },
        { status: 200 }
    );
}
Enter fullscreen mode Exit fullscreen mode

Now, we can use this data to send email templates using Novu Echo and React Email.

Code-first email notification workflow with Novu and React Email

Run this code snippet in your terminal to install the Novu SDK and Novu Echo to the Next.js project. We'll create the notification workflow with Novu Echo and pass a payload from the API server to the workflow, then into the React Email template.

npm install @novu/node @novu/echo
Enter fullscreen mode Exit fullscreen mode

Next, install React Email by running the following command:

npm install react-email @react-email/components -E
Enter fullscreen mode Exit fullscreen mode

Include the following script in your package.json file. The --dir flag gives React Email access to the email templates located within the project. In this case, the email templates are located in the src/emails folder.

{
    "scripts": {
        "email": "email dev --dir src/emails"
    }
}
Enter fullscreen mode Exit fullscreen mode

Run npm run email in your terminal. This command starts the React Email development server, allowing you to choose from the existing templates. Alternatively, you can create an emails folder containing an index.tsx file and copy this email template into it.

import {
    Body,
    Column,
    Container,
    Head,
    Heading,
    Hr,
    Html,
    Link,
    Preview,
    Section,
    Text,
    Row,
} from "@react-email/components";
import * as React from "react";

const EmailTemplate = () => (
    <Html>
        <Head />
        <Preview>Hello World</Preview>
        <Body style={main}>
            <Container style={container}>
                <Section style={header}>
                    <Row>
                        <Column style={headerContent}>
                            <Heading style={headerContentTitle}>Hello World</Heading>
                        </Column>
                    </Row>
                </Section>

                <Section style={content}>
                    <Text style={paragraph}>Hey Emil,</Text>
                    <Text style={paragraph}>Cool</Text>
                </Section>
            </Container>

            <Section style={footer}>
                <Text style={footerText}>
                    You&apos;re receiving this email because your subscribed to Newsletter
                    App
                </Text>

                <Hr style={footerDivider} />
                <Text style={footerAddress}>
                    <strong>Newsletter App</strong>, &copy;{" "}
                    <Link href='https://novu.co'>Novu</Link>
                </Text>
            </Section>
        </Body>
    </Html>
);

export default EmailTemplate;

const main = {
    backgroundColor: "#f3f3f5",
    fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif",
};

const headerContent = { padding: "20px 30px 15px" };

const headerContentTitle = {
    color: "#fff",
    fontSize: "27px",
    fontWeight: "bold",
    lineHeight: "27px",
};

const paragraph = {
    fontSize: "15px",
    lineHeight: "21px",
    color: "#3c3f44",
};

const divider = {
    margin: "30px 0",
};

const container = {
    width: "680px",
    maxWidth: "100%",
    margin: "0 auto",
    backgroundColor: "#ffffff",
};

const footer = {
    width: "680px",
    maxWidth: "100%",
    margin: "32px auto 0 auto",
    padding: "0 30px",
};

const content = {
    padding: "30px 30px 40px 30px",
};

const header = {
    borderRadius: "5px 5px 0 0",
    display: "flex",
    flexDireciont: "column",
    backgroundColor: "#2b2d6e",
};

const footerDivider = {
    ...divider,
    borderColor: "#d6d8db",
};

const footerText = {
    fontSize: "12px",
    lineHeight: "15px",
    color: "#9199a1",
    margin: "0",
};

const footerLink = {
    display: "inline-block",
    color: "#9199a1",
    textDecoration: "underline",
    fontSize: "12px",
    marginRight: "10px",
    marginBottom: "0",
    marginTop: "8px",
};

const footerAddress = {
    margin: "4px 0",
    fontSize: "12px",
    lineHeight: "15px",
    color: "#9199a1",
};
Enter fullscreen mode Exit fullscreen mode

At this point, you should have an email template ready to integrate into Novu Echo. 🚀

To get started with Novu Echo, close the React Email server, and run the code snippet below. It opens the Novu Dev Studio in your browser.

npx novu-labs@latest echo
Enter fullscreen mode Exit fullscreen mode

Create an echo folder containing a client.ts file within the Next.js app folder and copy this code snippet into the file.

import { Echo } from "@novu/echo";
import { renderEmail } from "@/emails";

interface EchoProps {
    step: any;
    payload: {
        subject: string;
        message: string;
        firstName: string;
    };
}
export const echo = new Echo({
    apiKey: process.env.NEXT_PUBLIC_NOVU_API_KEY!,
    devModeBypassAuthentication: process.env.NODE_ENV === "development",
});

echo.workflow(
    "newsletter",
    async ({ step, payload }: EchoProps) => {
        await step.email(
            "send-email",
            async () => {
                return {
                    subject: `${payload ? payload?.subject : "No Subject"}`,
                    body: renderEmail(payload),
                };
            },
            {
                inputSchema: {
                    type: "object",
                    properties: {},
                },
            }
        );
    },
    {
        payloadSchema: {
            type: "object",
            properties: {
                message: {
                    type: "string",
                    default: "This is a test message from Newsletter",
                },
                subject: { type: "string", default: "Message from Newsletter App" },
                firstName: { type: "string", default: "User" },
            },
            required: ["message", "subject", "firstName"],
            additionalProperties: false,
        },
    }
);
Enter fullscreen mode Exit fullscreen mode

The code snippet defines a Novu notification workflow named newsletter, which accepts a payload containing the email subject, message, and the recipient's first name. This payload contains the necessary data for the React Email template.

Update the React Email template to receive and display the payload data:

const EmailTemplate = ({
    message,
    subject,
    firstName,
}: {
    message: string;
    subject: string;
    firstName: string;
}) => (
    <Html>
        <Head />
        <Preview>{subject}</Preview>
        <Body style={main}>
            <Container style={container}>
                <Section style={header}>
                    <Row>
                        <Column style={headerContent}>
                            <Heading style={headerContentTitle}>{subject}</Heading>
                        </Column>
                    </Row>
                </Section>

                <Section style={content}>
                    <Text style={paragraph}>Hey {firstName},</Text>
                    <Text style={paragraph}>{message}</Text>
                </Section>
            </Container>

            <Section style={footer}>
                <Text style={footerText}>
                    You&apos;re receiving this email because your subscribed to Newsletter
                    App
                </Text>

                <Hr style={footerDivider} />
                <Text style={footerAddress}>
                    <strong>Newsletter App</strong>, &copy;{" "}
                    <Link href='https://novu.co'>Novu</Link>
                </Text>
            </Section>
        </Body>
    </Html>
);

//👇🏻 passes the payload as props into the component
export function renderEmail(inputs: {
    message: string;
    subject: string;
    firstName: string;
}) {
    return render(<EmailTemplate {...inputs} />);
}
Enter fullscreen mode Exit fullscreen mode

The <EmailTemplate/> component accepts the email's message, subject, and recipient's first name as props to replace the existing variables within the component.

Next, you need to create an API route for Novu Echo. Within the api folder, create an email folder containing a route.ts file and copy the provided code snippet below into the file.

import { serve } from "@novu/echo/next";
import { echo } from "../../echo/client";

export const { GET, POST, PUT } = serve({ client: echo });
Enter fullscreen mode Exit fullscreen mode

Make sure you have run npx novu-labs@latest echo in your terminal. This command will automatically open the Novu Dev Studio at this URL: http://localhost:2022/ in your browser. Here, you can select the exact API endpoint you served Novu Echo and preview your email templates before adding to the Novu Cloud Platform.

Once you can preview your email template within the Novu Dev Studio, you can save the workflow to your Novu dashboard. Click the Sync to Cloud button at the top right side of the page.

Novu Dev Studio

The Sync to Cloud button triggers a pop-up that provides instructions on how to push your workflow to the Novu Cloud.

To proceed, run the following code snippet in your terminal. This will generate a unique URL representing a local tunnel between your development environment and the cloud environment.

npx localtunnel --port 3000
Enter fullscreen mode Exit fullscreen mode

Novu Dev Studio 2

Copy the generated link along with your Echo API endpoint into the Echo Endpoint field and then click the Create Diff button.

https://<LOCAL_TUNNEL_URL>/<ECHO_API_ENDPOINT>
Enter fullscreen mode Exit fullscreen mode

Afterward, scroll down and select Deploy Changes to push the workflow to the cloud environment.

ScreenRecording2024-04-14at07.47.11-ezgif.com-video-to-gif-converter.gif

You can also preview your workflow on the Novu Cloud by clicking the Test your workflows link from the notification alert. Congratulations! Your workflow has been successfully deployed.

Now, you can start sending emails via the workflow. Open the api/send API route and copy the provided code snippet below into the route.ts file.

import { NextRequest, NextResponse } from "next/server";
import { SubscribersData } from "../../util";
import { Novu } from "@novu/node";

//👇🏻 Novu SDK for sending emails
const novu = new Novu(process.env.NOVU_API_KEY!);

export async function POST(req: NextRequest) {
    const { subject, message, subscribers } = await req.json();

    subscribers.forEach(async (subscriber: SubscribersData) => {
        const { data } = await novu.trigger("newsletter", {
            to: {
                subscriberId: subscriber.subscriberId,
                email: subscriber.email,
                firstName: subscriber.firstName,
                lastName: subscriber.lastName,
            },
            payload: {
                subject,
                message,
                firstName: subscriber.firstName,
            },
        });
        console.log(data.data);
    });

    return NextResponse.json(
        {
            message: "Emails sent successfully",
            success: true,
        },
        { status: 200 }
    );
}
Enter fullscreen mode Exit fullscreen mode

The code snippet uses the Novu SDK to send an email to all the subscribers within the application.

Congratulations! 🎉You've completed the project for this tutorial.

Conclusion

So far, you've learnt how to implement multiple authentication methods with Firebase, store and retrieve data from Firebase, create email templates with React Email, and send them via Novu.

You also learned how to leverage the code-first notification workflow approach to build notification workflows within the code while giving your non-technical teammates full control over content and behaviour.

Novu is your best choice to add various notification channels to your applications, including Chat, SMS, Email, Push, and In-app notifications.

The source code for this tutorial is available here:
https://github.com/novuhq/newsletter-app-with-novu-echo

Thank you for reading!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .