Create e-mail subscription panel with Refine and Strapi.

Melih Ekinci - Oct 25 '21 - - Dev Community

We will make a web application that allows you to quickly create subscribers and send emails to your subscribers in a simple way. We’ll use refine to develop the frontend easily and strapi for backend solutions.

Let's start by creating our strapi and refine projects.

Creating API with Strapi

npx create-strapi-app strapi-email-subscription-api --quickstart
Enter fullscreen mode Exit fullscreen mode

After the project is loaded, the admin panel will open automatically open in the browser. We need to create an admin user in order to log in to the strapi.

Image description

With the information we will create here, we can now enter the strapi and start shaping our backend.

After logging into the Strapi interface, we have two collection models that we need to create for our e-mail subscription project.

We will create these collections from the Collection-Types Builder part of the strapi.

Subscribers

  • name text field
  • email Email

Image description

Messages

  • subject text field
  • text text field

Image description

With these collections and features we have created, we can now create subscribers, delete them and make changes to them.

Creating panel with refine

Now let's refine the subscription panel. With superplate, we can quickly create a refine project

npx superplate-cli email-subscription-panel
Enter fullscreen mode Exit fullscreen mode

Select the following options to complete the CLI wizard:

? Select your project type:
> refine

? Package manager:
> Npm

? Do you want to customize the theme?:
> No (Ant Design default theme)

? Data Provider: 
> Strapi

? Do you want to customize layout?:
> Yes, I want

? i18n - Internationalization: 
> No
Enter fullscreen mode Exit fullscreen mode

After the upload is finished, let's go into our project and see how it looks.

cd email-subscription-panel
npm run dev
Enter fullscreen mode Exit fullscreen mode

This is a example Refine project:

Image description

Let's list our messages and subscribers with refine. Here are the changes we need to make:

  • Change Strapi API URL from refine
  • Adding resources according to the collection name we created in Strapi

/App.tsx

import { Refine, Resource } from "@pankod/refine";

import "@pankod/refine/dist/styles.min.css";
import { DataProvider } from "@pankod/refine-strapi";
import strapiAuthProvider from "authProvider";
import { Header, Layout, OffLayoutArea } from "components";


function App() {
 - const API_URL = "your-strapi-api-url";
 + const API_URL = "http://localhost:1337";

  const { authProvider, axiosInstance } = strapiAuthProvider(API_URL);
  const dataProvider = DataProvider(API_URL, axiosInstance);
  return (
    <Refine
      dataProvider={dataProvider}
      authProvider={authProvider}
      Header={Header}
      Layout={Layout}
      OffLayoutArea={OffLayoutArea}
    >
      <Resource
        name="subscribers"/>

      <Resource
        name="messages"/>
    </Refine>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Image description

After adding the resources, we need to define a user in the strapi in order to be able to login to the refine.

Image description

Let's login with this user we created

Image description

We can now list subscribers and messages and make changes to our list. Before doing this, let's create test users and messages on the strapi side.

Image description

Create SubscriberList.tsx and MessagesList.tsx file under the pages folder. Then, let's create our component as follows with the components and hooks that come with refine.

/src/pages/subscriber/SubscriberList.tsx

import React from "react";
import {
  useTable,
  List,
  Table,
  DateField,
  DeleteButton,
  IResourceComponentsProps,
} from "@pankod/refine";

import { ISubscriber } from "interfaces";

export const SubscriberList: React.FC<IResourceComponentsProps> = () => {
  const { tableProps } = useTable<ISubscriber>();
  return (
    <List>
      <Table {...tableProps} rowKey="id">
        <Table.Column dataIndex="id" title="Id" />
        <Table.Column dataIndex="name" title="Name" />
        <Table.Column dataIndex="email" title="E-mail" />
        <Table.Column
          dataIndex="created_at"
          title="createdAt"
          render={(value) => <DateField format="LLL" value={value} />}
        />
        <Table.Column<ISubscriber>
          title="Unsubscribe"
          dataIndex="actions"
          render={(_, record): React.ReactNode => {
            return (
              <DeleteButton size="small" recordItemId={record.id} hideText />
            );
          }}
        />
      </Table>
    </List>
  );
};

Enter fullscreen mode Exit fullscreen mode

/src/pages/mail/MessageList.tsx

import React from "react";
import {
  useTable,
  List,
  Table,
  DateField,
  IResourceComponentsProps,
} from "@pankod/refine";

import { IMail } from "interfaces";

export const MessageList: React.FC<IResourceComponentsProps> = () => {
  const { tableProps } = useTable<IMail>();
  return (
    <List>
      <Table {...tableProps} rowKey="id">
        <Table.Column dataIndex="id" title="Id" />
        <Table.Column dataIndex="subject" title="Subject" />
        <Table.Column dataIndex="text" title="Body" />
        <Table.Column 
          dataIndex="created_at"
          title="createdAt"
          render={(value) => <DateField format="LLL" value={value} />}
        />
      </Table>
    </List>
  );
};
Enter fullscreen mode Exit fullscreen mode

/src/interfaces/intex.d.ts


export interface ISubscriber {
  id: any;
  name: string;
  email: string;
  created_at: string;
}

export interface IMail {
  subject: string;
  text: string;
  to: string;
  create_at: string;
}
Enter fullscreen mode Exit fullscreen mode

In this component:

We used refine's list and table to show our subscribers and messages.

Now let's see how our subscriber panel looks like:

Subscriber:

Image description

Messages:

Image description

As you can see, we were able to list our subscribers and e-mails very simply with refine. Now let's examine how we can create subscribers and messages from our interface.

/src/pages/subscriber/create.tsx

import {
  Create,
  Form,
  Input,
  useForm,
  IResourceComponentsProps,
} from "@pankod/refine";

import { ICreateSubscriber } from "interfaces";

export const CreateSubscriber: React.FC<IResourceComponentsProps> = () => {
  const { formProps, saveButtonProps } = useForm<ICreateSubscriber>();

  return (
    <Create saveButtonProps={saveButtonProps}>
      <Form {...formProps} layout="vertical">
        <Form.Item label="Name" name="name">
          <Input />
        </Form.Item>
        <Form.Item
          label="E-mail"
          name="email"
          rules={[
            {
              required: true,
            },
          ]}
        >
          <Input />
        </Form.Item>
      </Form>
    </Create>
  );
};
Enter fullscreen mode Exit fullscreen mode

/src/pages/mail/create.tsx

import React, { useState } from "react";
import {
  Create,
  Form,
  Input,
  useForm,
  IResourceComponentsProps,
} from "@pankod/refine";

import ReactMarkdown from "react-markdown";
import ReactMde from "react-mde";
import "react-mde/lib/styles/css/react-mde-all.css";
import { IMail } from "interfaces";

export const MailCreate: React.FC<IResourceComponentsProps> = () => {
  const { formProps, saveButtonProps } = useForm<IMail>();
  const [selectedTab, setSelectedTab] = useState<"write" | "preview">("write");

  return (
    <Create saveButtonProps={saveButtonProps}>
      {console.log("create")}
      <Form {...formProps} layout="vertical">
        <Form.Item
          label="Subject"
          name="subject"
          rules={[
            {
              required: true,
            },
          ]}
        >
          <Input />
        </Form.Item>
        <Form.Item
          label="Body"
          name="text"
          rules={[
            {
              required: true,
            },
          ]}
        >
          <ReactMde
            selectedTab={selectedTab}
            onTabChange={setSelectedTab}
            generateMarkdownPreview={(markdown: any) =>
              Promise.resolve(<ReactMarkdown>{markdown}</ReactMarkdown>)
            }
          />
        </Form.Item>
        <Form.Item
          label="To"
          name="to"
          rules={[
            {
              required: true,
            },
          ]}
        >
          <Input />
        </Form.Item>
      </Form>
    </Create>
  );
};
Enter fullscreen mode Exit fullscreen mode

Using refine's form and create components, we can now create subscribers and messages with refine.

Image description

We're done with our panel. We can now list, create and delete subscribers. Finally, the step of sending real mails using our panel and strapi is left. Let's see how we do this.

Strapi E-mail Plugin

In order to send mail via Strapi, we need to install the strapi-email plugin in our api project that we created above.

Let's open our API project that we created and download the email plugin.

cd strapi-email-subscription-api
npm install strapi-provider-email-sendgrid --save
Enter fullscreen mode Exit fullscreen mode

After installing your plugin you will need to add some settings in config/plugins.js. If this file doesn't exists, you'll need to create it.

Configure your provider

Path — ./config/plugins.js

module.exports = ({ env }) => ({
    email: {
      provider: 'sendgrid',
      providerOptions: {
        apiKey: env('SENDGRID_API_KEY'),
      },
      settings: {
        defaultFrom: 'your-email-adress',
        defaultReplyTo: 'your-email-adress',
        testAddress: 'your-email-adress',
      },
    },
  });
Enter fullscreen mode Exit fullscreen mode

💡 TIP: Strapi sends emails via sendgrid. That's why you need to create a SendGrid account and get an api-key.

Now, let's send the text and subject in the collection of messages we created over the strapi as parameters to the send() function of the email plugin.

api/messages/controllers/messages.js

const { parseMultipartData, sanitizeEntity } = require("strapi-utils");

module.exports = {
  async create(ctx) {
    let entity;
    if (ctx.is("multipart")) {
      const { data, files } = parseMultipartData(ctx);
      entity = await strapi.services.messages.create(data, { files });
    } else {
      entity = await strapi.services.messages.create(ctx.request.body);
    }

    entity = sanitizeEntity(entity, { model: strapi.models.messages });

    const { subject, text } = entity;

    const worker = (await strapi.services.subscribers.find()).map(
      (subscriber) => {
        let to = subscriber.email;

        return strapi.plugins["email"].services.email.send({
          subject,
          text,
          to,
        });
      }
    );

    await Promise.all(worker);

    return entity;
  },
};
Enter fullscreen mode Exit fullscreen mode

Our project is finished. Let's try it now.

Image description

Let's send the same e-mail to our subscribers shown in the picture at once.

Image description

Image description

Sending mail was successful. As you can see, we were able to send the same email to all subscribers by sending a single email.

Here is repo

For more information about Refine: https://refine.dev/

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