DEV Community

Cover image for Web3 Newsletter Creation Made Easy with Mailchain: A Step-by-Step Guide
Vedant Chainani
Vedant Chainani

Posted on • Updated on • Originally published at blog.vedantc.dev

Web3 Newsletter Creation Made Easy with Mailchain: A Step-by-Step Guide

Overview

Traditional newsletters often face challenges such as a lack of privacy, security, and user control over data. However, Mailchain provides a solution to these issues. With Mailchain, all messages are secured with end-to-end encryption, giving you full ownership of your keys and ensuring that your data remains private. Messages are also encrypted and stored on decentralized storage, safeguarding your privacy and providing enhanced security.

By the end of this tutorial, you will have built a simple yet powerful newsletter service that allows users to join your newsletter using their Ethereum address. You will learn how to keep track of subscribers and send them a joining email using Mailchain.

You can find an example of the final result here

https://github.com/Envoy-VC/mailchain-newsletter

GitHub logo Envoy-VC / mailchain-newsletter

A Next.js application that leverages Mailchain and GraphQL to enable users to subscribe to newsletters and receive welcome emails.

As you work through the tutorial, you'll be usingΒ Next.jsΒ as our frontend framework, RainbowKit for wallet authentication and TailwindCSS for Styling. Also, we will be using Hygraph as our Content Management system(CMS) to keep track of our subscribers. No worries if you're not well-versed in these technologies - we'll provide step-by-step instructions with code snippets throughout the tutorial.


Prerequisites

Before you begin with this tutorial, make sure you have the following:

  1. NodeJS installed on your local machine.

  2. A Mailchain account created for development and testing purposes.

  3. An Ethereum address registered in Mailchain to your development and testing account.

  4. An account created on Hygraph, a headless CMS that we will use to store the newsletter subscribers.


Step 1 - Clone the starter repository

The starter repository contains a basic boilerplate for a Next.js app with Rainbow Kit and Tailwind CSS configured. This starter repository provides a solid foundation for building a web3-based-applications

  1. Open your terminal and navigate to the directory where you want to clone the repository.

  2. Run the following command to clone the repository:

    git clone https://github.com/Envoy-VC/dapp-kit.git
    
  3. Once the cloning is complete, navigate into the cloned directory:

    cd dapp-kit
    
  4. Now runΒ npmΒ in this directory to install all the dependencies.

    npm install
    

Step 2 - Creating our Database

  1. Create a New Project in Hygraph - Navigate to the Hygraph dashboard and click on "Add Project" to create a new project. You can give your project any name, provide a description, and select a region that's closest to you. Once done, click on "Add Project" to create your project.

    Creating a Hygraph Project

  2. Create a Schema for Your Data Next, let's create a schema, which is a layout for our data. Go to the "Schema" section in the left sidebar of Hygraph and click on "Add Model". Fill in the details for the model, as shown in the picture below, and feel free to add any description. Make sure that the name and API IDs match, as we will be using them to query the data. Click on "Add Model" to create your schema.

    Create a new model

  3. Let's add some fields to our model. In the right sidebar of Hygraph, click on Single Line Text option, which will add a simple text input to our model. Give it a name, such as "Address", and provide a description. Under the "Validations" tab, ensure that the field is set to be unique and required.

    Address fieldValidations

  4. Set Permissions for the API Under the left sidebar, go to "Project Settings" and navigate to the "Public Content API" section. Here, we need to set permissions for the API to query and mutate data from our front end. Click on "Add Permissions" and select all the checkboxes. Then click on "Add" to set the permissions.

    Set Permissions for Content API

  5. Save the Content API Link Scroll to the top of the page until you see a "Content API" link. We will be using this link to communicate with our database. Copy this link and save it somewhere for future reference.

    Content API Link

Now that we have initialized our Hygraph database and created a schema for our data, we are ready to move on to the next step


Step 3 - Configure environment variables

Now, let's configure our environment variables to set values that will be passed to our code. It's important to avoid storing environment variables in source control, as it can expose sensitive information. In this tutorial, we have a .gitignore file that tells Git to ignore the existing environment files, preventing any changes to them from being stored in Git. However, make sure to add any new environment files to the .gitignore as well.

# env files
.env 
.env.*
Enter fullscreen mode Exit fullscreen mode

To create a new environment file, go to the root directory of your app and create a file called .env.local. Open it in your code editor and include the following values:

NEXT_PUBLIC_MAILCHAIN_SECRET_KEY='mailchain-recovery-phrase-here'
NEXT_PUBLIC_HYGRAPH_URL='hygraph-content-url-here'
Enter fullscreen mode Exit fullscreen mode

Make sure to replace the placeholder values with your values:

  • NEXT_PUBLIC_MAILCHAIN_SECRET_KEY - your secret recovery phrase for your development Mailchain account

  • NEXT_PUBLIC_HYGRAPH_URL - the Content API link that you copied at the end of Step 2

By setting these environment variables, we will be able to securely access our Mailchain account and Hygraph database from our application without exposing any sensitive information in our code.


Step 4 - Creating an API Route

Now that we have configured our environment variables and installed the Mailchain SDK, we are ready to create an API route for sending emails to users who have joined our newsletter.

To get started, we need to create an HTML template for the email. Let's create a folder called "constants" under the root directory of our project, and inside it, we'll create a file called "index.js" where we can paste the HTML content for the email. This HTML template will be used to generate the content of the email that will be sent to the subscribers of our newsletter.

export const message = `
<div style="max-width: 600px; font-family: Arial, sans-serif; padding: 20px">
    <div
        style="
            max-width: 600px;
            margin: 0 auto;
            background-color: #ffffff;
            padding: 20px;
            border-radius: 5px;
            box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
        "
    >
        <h1
            style="
                background: linear-gradient(#30cfd0, #c43ad6);
                -webkit-background-clip: text;
                -webkit-text-fill-color: transparent;
                font-size: 36px;
                margin-top: 16px;
                padding-bottom: 16px;
                font-weight: 800;
            "
        >
            GM!
        </h1>
        <p style="color: #333333; line-height: 1.5; padding-bottom: 8px">
            Even if it’s not morning where you are, it’s morning in Web3! 🌞
        </p>
        <p style="color: #333333; line-height: 1.5; padding-bottom: 8px">
            Thank you for being a part of WAGMI Weekly - Your Web3 Fun-dose!
        </p>
        <p style="color: #333333; line-height: 1.5; padding-bottom: 8px">
            Get ready for another exciting edition of our newsletter, packed with fun,
            laughter, and expert analysis of all things Web3!
        </p>
        <p style="color: #333333; line-height: 1.5; padding-bottom: 8px">
            As always, we'll be serving up your weekly dose of Web3 news and
            entertainment, with a touch of coffee β˜•οΈ
        </p>
        <div style="display: inline-block; margin-top: 10px; margin-bottom: 10px">
            <img
                src="https://i.ibb.co/KNF9zhg/johnny-test-bling-bling-boy.gif"
                alt="Coffee"
                style="max-width: 600px; height: auto"
            />
        </div>
        <p style="color: #999999; font-style: italic">
            "Web3 without coffee is like a blockchain without blocks!"
        </p>
        <div style="display: block; margin-top: 20px; text-align: center">
            <a
                href="https://example.com"
                target="_blank"
                style="
                    display: inline-block;
                    padding: 10px 20px;
                    background-color: #0066cc;
                    color: #ffffff;
                    text-decoration: none;
                    border-radius: 5px;
                "
                >Read Now!</a
            >
        </div>
        <div class="footer">
            <p style="color: #333333; line-height: 1.5; padding-bottom: 8px">
                WAGMI Weekly - Your Web3 Fun-dose!
            </p>
            <p style="color: #333333; line-height: 1.5; padding-bottom: 8px">
                Questions or comments? Contact us
                <a href="mailto:info@example.com">here</a>.
            </p>
        </div>
    </div>
</div>
`;
Enter fullscreen mode Exit fullscreen mode

Next, we'll create the API route itself. We'll create a new file called "join.js" inside the "pages/api/" folder of our project. In this file, we'll import the necessary modules and files that we'll need to send emails using the Mailchain SDK.

import { Mailchain } from '@mailchain/sdk';
import { message } from '@/constants';

const secretRecoveryPhrase = process.env.NEXT_PUBLIC_MAILCHAIN_SECRET_KEY;
Enter fullscreen mode Exit fullscreen mode

Once the imports are set up, we'll create an exported function called "handler" which will be the entry point for our API route. This function will be called every time an API call is made to this route. It takes two arguments, "req" and "res", which represent the request and response objects respectively.

export default async function handler(req, res) {
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Inside the "handler" function, we'll first extract the "address" from the request body. This will be the email address of the subscriber to whom we'll be sending the email. We'll then initialize Mailchain with the recovery phrase that we have set up in our environment variables.

export default async function handler(req, res) {
    const { address } = req.body;
Β  Β  const mailchain = Mailchain.fromSecretRecoveryPhrase(secretRecoveryPhrase);
}
Enter fullscreen mode Exit fullscreen mode

To handle any errors that may occur during the sending of the email, we'll use a try-catch block. If no address is provided in the request body, we'll throw an error indicating that the email address is required. If an address is provided, we'll use the "sendMail" method from the Mailchain SDK to send the email. This method takes several parameters, such as the sender, receiver, subject, and content of the email, which we'll pass as arguments.

export default async function handler(req, res) {
    // ...
    try {
        if (!address) res.status(400).json({ error: 'Address not found' });

        // Send Mail
        const { data, error } = await mailchain.sendMail({
            from: `envoy1084@lens@lens.mailchain.com`,
            to: [`${address}@ethereum.mailchain.com`],
            subject: 'Welcome to WAGMI Weekly!',
            content: {
                text: 'It’s official… We’re buddies now!',
                html: message,
            },
        });

        if (error) {
            res.status(500).json({ error: error });
        } else res.status(200).json({ data: data });
    } catch (error) {
        res.status(404).json({ error: error });
    }
}
Enter fullscreen mode Exit fullscreen mode

This is it, our API route is configured. Now let's move to the frontend part.


Step 5 - Creating Queries for communicating with CMS

Before we dive into building the frontend part of our application, we need to write some queries that will allow us to fetch data from Hygraph CMS. To do this, let's create a folder called "utils" in the root directory of our project, and inside it, we'll create a file called "query.js" where we'll define our queries.

To start, we'll need to install some packages that will help us with querying data. We can do this by running the following command in our terminal:

npm install graphql-request graphql
Enter fullscreen mode Exit fullscreen mode

Once the packages are installed, we'll need to import some modules and create a Hygraph client that we can use to call our queries. We'll set up our client as follows:

import { GraphQLClient, gql } from 'graphql-request';

export const hygraph = new GraphQLClient(process.env.NEXT_PUBLIC_HYGRAPH_URL);
Enter fullscreen mode Exit fullscreen mode

Now that our client is set up, we can define our queries. We'll create three queries, namely "CHECK_WALLET", "CREATE_SUBSCRIBER", and "PUBLISH_SUBSCRIBER", which will allow us to interact with the CMS.

  1. The CHECK_WALLET query will check if the user is already subscribed to the newsletter by returning the ID of the subscriber if they are subscribed.

    export const CHECK_WALLET = (address) => {
        const query = gql`
            {
                subscribers(where: { address: "${address}" }) {
                    id
                }
            }
        `;
        return query;
    };
    
  2. The "CREATE_SUBSCRIBER" query will post the address of the subscriber to the CMS, but the data will be in a draft state. We can define this query as follows:

    export const CREATE_SUBSCRIBER = (address) => {
        const query = gql`
            mutation CreateSubscriber {
                createSubscriber(
                    data: { address: "${address}"}) {
                    id
                }
            }
        `;
        return query;
    };
    
  3. The PUBLISH_SUBSCRIBER query will publish the previously posted data, making it visible to other users. We can define this query as follows:

    export const PUBLISH_SUBSCRIBER = (address) => {
        const query = gql`
            mutation PublishSubscriber {
                publishSubscriber(
                    where: { address: "${address}"}) {
                    id
                }
            }
        `;
        return query;
    };
    

By defining these queries, we have set up the functionality to interact with the CMS and perform actions such as checking if a user is subscribed, creating a subscriber, and publishing subscriber data. These queries will be used in our frontend application to fetch and manipulate data from Hygraph CMS seamlessly.


Step 6 - Creating our Newsletter Component

In this step, we'll create a new component called Newsletter.jsx inside the components directory, which will serve as our newsletter component. Let's start by importing the necessary modules and files that we'll need for our component:

import * as React from 'react';
import Image from 'next/image';

import { useSignMessage } from 'wagmi';
import { verifyMessage } from 'ethers/lib/utils';

import {
    hygraph,
    CHECK_WALLET,
    CREATE_SUBSCRIBER,
    PUBLISH_SUBSCRIBER,
} from '@/utils/query';

import newsletter from '../assets/newsletter.png';
Enter fullscreen mode Exit fullscreen mode

We'll also need to set up some state variables and references to store the loading state and address of the user:

const Newsletter = () => {
    const recoveredAddress = React.useRef('');
    const [loading, setLoading] = React.useState(false);
}
Enter fullscreen mode Exit fullscreen mode

Next, we'll use the signMessage variable from useSignMessage to create a function that will allow the user to sign a message. We'll use the onSuccess method to verify the signature and store the recovered address in the recoveredAddress variable. Then we'll call the joinNewsletter function with the userAddress passed as an argument:

const Newsletter = () => {

    // ...

    const { signMessage } = useSignMessage({
        message: 'Sign this message to subscribe to WAGMI Weekly',
        onSuccess(data, variables) {
            const address = verifyMessage(variables.message, data);
            recoveredAddress.current = address;
            joinNewsletter(recoveredAddress.current);
        },
        onError() {
            setLoading(false);
            // user rejected signature
        },
    });
}
Enter fullscreen mode Exit fullscreen mode

Now we'll create two functions called isSubscribed and createSubscriber, which we'll use to verify if the user is already subscribed and create a new subscriber:

const Newsletter = () => {

    // ...

    const isSubscribed = async (address) => {
        const query = CHECK_WALLET(address);
        const id = await hygraph.request(query);
        if (id.subscribers[0] === undefined) return false;
        else if (id.subscribers[0].id) return true;
    };

    const createSubscriber = async (address) => {
        const subscriberId = await hygraph.request(CREATE_SUBSCRIBER(address));
        const published = await hygraph.request(PUBLISH_SUBSCRIBER(address));
        return published.publishSubscriber.id;
    };

}
Enter fullscreen mode Exit fullscreen mode

Finally, we'll create the joinNewsletter function, which takes in a userAddress and checks if the user is an existing subscriber. If not, it adds the address to the subscriber list and calls the join API route to send the user a welcome mail. If the user is already subscribed, it throws an error:

const Newsletter = () => {

    // ...

    const joinNewsletter = async (userAddress) => {
        try {
            const existingSubscriber = await isSubscribed(userAddress);
            if (!existingSubscriber) {
                const id = await createSubscriber(userAddress);
                const response = await fetch('/api/join', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ address: userAddress }),
                });
                const data = await response.json();
                if (data.savedMessageId) console.log('Subscribed successfully');
            } else {
                console.log('Already subscribed');
                throw new Error('Already subscribed');
            }
        } catch (error) {
            console.log(error);
        } finally {
            setLoading(false);
        }
    };

}
Enter fullscreen mode Exit fullscreen mode

Next, we'll return the HTML content for our component and export the component:

const Newsletter = () => {

    // ...

    return (
        <section className='my-8 text-black'>
            <div className='container flex flex-col items-center p-4 mx-auto space-y-6 md:p-8'>
                <Image src={newsletter} alt='newsletter' width={96} height={96} />
                <p className='px-6 py-2 text-2xl text-gray-700 text-center font-bold sm:text-3xl md:text-4xl lg:max-w-2xl xl:max-w-4xl font-rubik'>
                    &quot;Bored of FOMO? Join WAGMI Weekly and never miss a crypto beat
                    again! Get your weekly dose of fun, laughter, and expert analysis on
                    all things Web3!&quot;
                </p>
                <button
                    className='text-xl text-gray-100  font-sans font-extrabold border-2 py-4 px-8 rounded-3xl semi-transparent-btn transition-all ease-in-out duration-700'
                    onClick={() => {
                        setLoading(true);
                        signMessage();
                    }}
                    disabled={loading}
                >
                    {loading ? 'Subscribing...' : 'Join Now'}
                </button>
            </div>
        </section>
    );
}

export default Newsletter;
Enter fullscreen mode Exit fullscreen mode

With this newsletter component, we've set up the functionality to allow users to join the newsletter by signing a message, verifying the signature, and adding the user to the subscriber list if they are not already subscribed. This component will be integrated into our frontend application to provide seamless newsletter subscription functionality to our users.

At last export the component from index.js file under the components folder

import Navbar from './Navbar';
import Newsletter from './Newsletter';

export { Navbar, Newsletter };
Enter fullscreen mode Exit fullscreen mode

and then use it in the home page pages/index.js

import { Navbar, Newsletter } from '@/components';

const Home = () => (
    <div>
        <Navbar />
        <Newsletter />
    </div>
);

export default Home;
Enter fullscreen mode Exit fullscreen mode

Step 7 - Testing

Now that we have implemented the newsletter component, we can test our application. Let's start by running our application in development mode:

npm run dev
Enter fullscreen mode Exit fullscreen mode

This will start our application and make it accessible at localhost:3000 in our web browser. We can now click on the "Join" button in our application to initiate the newsletter subscription process.

Homepage

Once the process is complete, we can check our Mailchain inbox, and we should see a welcome mail for joining the "WAGMI Weekly" newsletter. This will confirm that our newsletter subscription functionality is working as expected.

Mailchain Inbox


Conclusion

Congratulations πŸŽ‰ on successfully building your newsletter service using Mailchain, Next.js, and GraphQL as outlined in this article.

With the implementation of the newsletter component, you now have a seamless way for users to subscribe to your newsletter and receive welcome emails. You can customize and extend the functionality of this service by visiting the repository

What's Next?

Some ideas for future enhancements include improving user experience, adding subscription management features, personalizing newsletters, analyzing newsletter performance, and preparing for scalability. By continuing to optimize your newsletter service, you can create a powerful tool for engaging with your audience, promoting your content, and driving traffic to your website.


Top comments (0)