# Strapi Email Designer (v5)

Design your own email templates directly from the Strapi admin panel and use the magic to send programmatically email from your controllers / services.

## ❤️ Credits

Special thanks to:

- [Unlayer](https://unlayer.com/) for the amazing email editor
- [Strapi Plugin Email Designer](https://github.com/alexzaganelli/strapi-plugin-email-designer) for doing the heavy lifting for v3 and v4
- [Strapi Plugin Email Designer v5](https://github.com/BayBreezy/strapi-plugin-email-designer-5) for doing the heavy lifting for v5

## ⚙️ Version

This plugin is compatible with Strapi v5.

## ✅ Prerequisites

- Node v18+ (I recommend using [nvm](https://github.com/nvm-sh/nvm), there is a windows and mac/linux version)
- A Strapi v5 project (`npx create-strapi-app@latest my-project`)

## ⬇️ Installation

Install the plugin by runnig the following command:

```bash
npm install strapi-plugin-email-designer-v5
```

## 🎚️ Middleware Configuration

By default, Strapi's security is tight. You will need to add the following to your `config/middleware.ts` file:

```ts
{
  name: "strapi::security",
  config: {
    contentSecurityPolicy: {
      useDefaults: true,
      directives: {
        "script-src": ["'self'", "'unsafe-inline'", "editor.unlayer.com"],
        "frame-src": ["'self'", "editor.unlayer.com"],
        upgradeInsecureRequests: null,
      },
    },
  },
}
```

Ensure that you replace the `"strapi::security"` line in the file with the above.

## 🎚️ Plugin Configuration

The plugin configuration accepts most of the properties that can be passed to the Unlayer editor. You can get more information about them here: https://docs.unlayer.com/docs/getting-started#configuration-options

This is the type definition for the configuration:

```ts
import type { EmailEditorProps } from "react-email-editor";

export type EmailConfig = Pick<
  NonNullable<EmailEditorProps["options"]>,
  | "projectId"
  | "locale"
  | "appearance"
  | "user"
  | "mergeTags"
  | "designTags"
  | "specialLinks"
  | "tools"
  | "blocks"
  | "fonts"
  | "safeHtml"
  | "customCSS"
  | "customJS"
  | "textDirection"
>;
```

### 📧 Nodemailer

If you are going to send emails from the Strapi backend, you need to configure the email plugin. You can visit the docs [here](https://github.com/strapi/strapi/tree/main/packages/providers/email-nodemailer) to learn more

```bash
npm install @strapi/provider-email-nodemailer --save
```

### ⚙️ Default configuration

Some options are passed by default to the editor. Expand the following object to see the default configuration:

<details>
<summary style="font-weight: bold">Default configuration</summary>

```ts
{
  mergeTagsConfig: {
    autocompleteTriggerChar: "@",
    sort: false,
    delimiter: ["{{", "}}"],
  },
  appearance: {
    theme: "modern_light",
  },
  fonts: {
    showDefaultFonts: false,
  },
  tools: {
    image: {
      properties: {
        src: {
          value: {
            url: "https://picsum.photos/600/350",
          },
        },
      },
    },
  },
  mergeTags: {
    core: {
      name: "Core",
      mergeTags: {
        // Values that can be used in the Reset Password context
        resetPassword: {
          name: "Reset Password",
          mergeTags: {
            // User in the Reset Password context
            user: {
              name: "USER",
              mergeTags: {
                username: {
                  name: "Username",
                  value: "{{= USER.username }}",
                  sample: "john_doe",
                },
                email: {
                  name: "Email",
                  value: "{{= USER.email }}",
                  sample: "johndoe@example.com",
                },
              },
            },
            token: {
              name: "TOKEN",
              value: "{{= TOKEN }}",
              sample: "corresponds-to-the-token-generated-to-be-able-to-reset-the-password",
            },
            url: {
              name: "URL",
              value: "{{= URL }}",
              sample: "is-the-link-where-the-user-will-be-redirected-after-clicking-on-it-in-the-email",
            },
            serverUrl: {
              name: "SERVER_URL",
              value: "{{= SERVER_URL }}",
              sample: "is-the-absolute-server-url-(configured-in-server-configuration)",
            },
          },
        },
        // Values that can be used in the Email Addres Confirmation context
        addressConfirmation: {
          name: "Confirm Address",
          mergeTags: {
            // User in the Email Address Confirmation context
            user: {
              name: "USER",
              mergeTags: {
                username: {
                  name: "Username",
                  value: "{{= USER.username }}",
                  sample: "john_doe",
                },
                email: {
                  name: "Email",
                  value: "{{= USER.email }}",
                  sample: "johndoe@example.com",
                },
              },
            },
            code: {
              name: "CODE",
              value: "{{= CODE }}",
              sample: "corresponds-to-the-CODE-generated-to-be-able-confirm-the-user-email",
            },
            url: {
              name: "URL",
              value: "{{= URL }}",
              sample:
                "is-the-Strapi-backend-URL-that-confirms-the-code-(by-default-/auth/email-confirmation)",
            },
            serverUrl: {
              name: "SERVER_URL",
              value: "{{= SERVER_URL }}",
              sample: "is-the-absolute-server-url-(configured-in-server-configuration)",
            },
          },
        },
      },
    },
    mustache: {
      name: "Mustache",
      mergeTags: {
        basic: {
          name: "Basic Output",
          mergeTags: {
            raw: {
              name: "Display Raw Content",
              value: "{{{REPLACE_ME}}}",
            },
            output: {
              name: "Regular Output",
              value: "{{REPLACE_ME}}",
            },
            dottedOutput: {
              name: "Dot notation for Output",
              value: "{{REPLACE_ME.NESTED_VALUE}}",
            },
          },
        },
        loops: {
          name: "Loops",
          mergeTags: {
            raw: {
              name: "Display Raw Content in Loop",
              value: "{{#ARRAY_OR_OBJECT_TO_ITERATE}}\n{{{REPLACE_ME}}}\n{{/ARRAY_OR_OBJECT_TO_ITERATE}}",
            },
            output: {
              name: "Regular Output in Loop",
              value: "{{#ARRAY_OR_OBJECT_TO_ITERATE}}\n{{REPLACE_ME}}\n{{/ARRAY_OR_OBJECT_TO_ITERATE}}",
            },
            dottedOutput: {
              name: "Dot notation for Output in Loop",
              value:
                "{{#ARRAY_OR_OBJECT_TO_ITERATE}}\n{{REPLACE_ME.NESTED_VALUE}}\n{{/ARRAY_OR_OBJECT_TO_ITERATE}}",
            },
          },
        },
      },
    },
  },
}
```

</details>

<br/>

If you wish to pass in any configuration, you can do so in the `config/plugins.ts` file. Here is an example of how to pass in a custom configuration:

```ts
// You can import the EmailConfig type from the plugin
import type { EmailConfig } from "strapi-plugin-email-designer-v5/dist/server/src";

export default ({ env }) => ({
  // You need Email configured if you are gonna send emails from the Strapi backend
  // This is a simple configuration for development using MailDev
  email: {
    config: {
      provider: "nodemailer",
      providerOptions: {
        host: "0.0.0.0",
        port: 1025,
        ignoreTLS: true,
      },
    },
  },
  // This is the configuration for the Email Designer plugin
  "email-designer-v5": {
    enabled: true,
    // Your custom configuration here
    config: {
      // Here the Merge Tags defined will be merged with the defaults above
      mergeTags: {
        company: {
          name: "Company",
          mergeTags: {
            name: {
              name: "Company Name",
              value: "ACME Corp",
              sample: "ACME Corp",
            },
          },
        },
      },
    } as EmailConfig,
  },
});
```

## ▶️ Usage

After installing & configuring your app for the plugin, you can access the email designer by navigating to the `Email Designer (v5)` section in the admin panel.

![Admin Area](./imgs/custom.png)

### ✅ Creating a new design

Click on the `Create a new email` button to start a new design.

### ✅ Importing a design

You can import a set of designs that were exported by the platform by clicking on the `Import Email Templates` button located at the bottom right of the page.

### ✅ Exporting a design

You can export all your designs by clicking the `Export Email Templates` button located at the bottom right of the page.

### ✅ Importing a Single Design

When you start a new design, a button will be located in the top right called `Import` that allows you to import a single design. If the json file is valid Unlayer exported design, it will be imported into the editor.

### ✅ Saving a design

Once you are done editing your design, you can click on the `Save` button located at the top right of the page. The `Template Reference ID`, `Template Name`, & `Subject` fields are required to save the design.(Well not the last two lol)

The `Template Reference ID` is a unique identifier for the design. It is used to reference the design when sending emails programmatically.

## { } Mustache

The backend uses Mustache to render the templates. You can visit the [Mustache documentation](https://mustache.github.io/mustache.5.html) to learn more about how to use it.

## 💻 Programmatically Sending Emails

You can send emails programmatically by using the `email-designer-v5` plugin. Here is an example of how to send an email:

> [!IMPORTANT]  
> This is tested the `index.ts` file of the `src` folder of my strapi project.

```ts
// import type { Core } from '@strapi/strapi';

export default {
  /**
   * An asynchronous register function that runs before
   * your application is initialized.
   *
   * This gives you an opportunity to extend code.
   */
  register(/* { strapi }: { strapi: Core.Strapi } */) {},

  /**
   * An asynchronous bootstrap function that runs before
   * your application gets started.
   *
   * This gives you an opportunity to set up your data model,
   * run jobs, or perform some special logic.
   */
  async bootstrap(/* { strapi }: { strapi: Core.Strapi } */) {
    try {
      await strapi
        .plugin("email-designer-v5")
        .service("email")
        .sendTemplatedEmail(
          {
            // required
            // This can also be an array of email addresses
            to: "to@example.com",
            // Optional
            cc: ["zez@jakce.ad", "ilez@gevcanuso.la"],
            // Optional
            bcc: ["fud@darfuv.py"],
            // optional if /config/plugins.js -> email.settings.defaultFrom is set
            from: "from@example.com",
            // optional if /config/plugins.js -> email.settings.defaultReplyTo is set
            replyTo: "reply@example.com",
            // optional array of files
            attachments: [],
          },
          {
            // required - Ref ID defined in the template designer (won't change on import)
            templateReferenceId: 20,
            // If provided here will override the template's subject.
            // Can include variables like `Thank you for your order {{= USER.firstName }}!`
            subject: `Thank you for your order`,
          },
          {
            // this object must include all variables you're using in your email template
            USER: { firstName: "John", lastName: "Doe" },
            order: {
              products: [
                { name: "Article 1", price: 9.99 },
                { name: "Article 2", price: 5.55 },
              ],
            },
            shippingCost: 5,
            total: 20.54,
          }
        );
      strapi.log.info("Email sent");
    } catch (error) {
      strapi.log.error(error);
    }
  },
};
```

This is what the email looked like in the MailDev client

![MailDev Test Results](./imgs/test-results.jpeg)

## 🚀 Available Services

Here is a list of the services that the plugin provides. You can call upon them in your controllers or services(or wherever you want).

### ✅ Email Services

#### `sendTemplatedEmail`

You will probably use this one a lot.

```ts
await strapi
  .plugin("email-designer-v5")
  .service("email")
  .sendTemplatedEmail(EMAIL_OPTIONS_OBJECT, EMAIL_TEMPLATE_OBJECT, DATA);
```

#### `compose`

I didn't get why this service was deprecated in the original plugin, but i found it useful for sending PDF data to my frontend.

```ts
await strapi.plugin("email-designer-v5").service("email").compose({ templateReferenceId, data });
```

This returns the email HTML and Text with the data merged.

```ts
{
  html: "<html>...</html>",
  text: "..."
}
```

## 🛟 Contributing

I am SUPER new to React. I have been using Nuxt all my life. If you see where I could have done something better in the admin area, please let me know. I am open to suggestions.

Just fork the repo, make your changes, and submit a PR. I will review it as soon as I can. I am open to learning new things.

## ✍🏽 License

MIT

I mean, I am not the original author of the plugin. I just made it compatible with Strapi v5. So, I am not sure if I can license it. But, I will keep it MIT for now.

## 🎆 Images

Image of the Core Tab in the Admin UI

![Image of the Core Tab in the Admin UI](imgs/core.png)

Image of the Custom Email Tab in the Admin UI

![Image of the Custom Email Tab in the Admin UI](imgs/custom.png)

Image of the Editor wit imported design

![Image of the Editor wit imported design](imgs/design.jpeg)

Image of Email in MailDev

![Image of Email in MailDev](imgs/test-results.jpeg)
