Resend - How to add an Email API Provider to Novu
Tags: javascript, notification, email, programming
Published: 2023-03-06 06:33:43

**TL;DR:** There is a popular saying that goes like this: _**You never really know something until you teach it to someone else**_.

In this tutorial, I'll show you how I added a new **email API provider** to Novu. In this scenario, I added [Resend](https://resend.com/docs/introduction). 

## Introduction to Novu

Novu is an open-source service that provides a unified API and components to send notifications through multiple channels, including In-App, Push, Email, SMS, and Chat. 

With Novu, you never need to roll your own [in-app notification](https://docs.novu.co/notification-center/getting-started) or manually integrate dozens of communication APIs in your app.

![In-app Notification Center](https://res.cloudinary.com/practicaldev/image/fetch/s--nshlTtHJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yn90yvsd87tgik03c08v.gif)


## Get Started

Head over to GitHub to clone the [Novu repo](https://github.com/novuhq/novu). Feel free to star the repo while you are at it. :wink:

Next, create a new branch from the `next` branch.

```bash
git checkout -b add-resend-email-provider next
```

Install the packages in the Novu project:

```bash
npm install
```

### Set Up New Email Provider

Run the command below to seamlessly create the templates needed to add a new email provider.

```bash
npm run generate:provider
```

It generates a prompt like so:

```bash
> generate:provider
> npx hygen provider new

? What type of provider is this? …
❯ EMAIL
  SMS
  PUSH
  CHAT
```

- Select **EMAIL** as the provider type.
- Add the name of the email API provider. Here, I added `resend`.

Once you hit `Return` or `Enter` on your keyboard, the templates will be generated. You should see them in your terminal like so:

```
added: providers/resend/.czrc
added: providers/resend/.eslintrc.json
added: providers/resend/.gitignore
added: providers/resend/jest.config.js
added: providers/resend/package.json
added: providers/resend/README.md
added: providers/resend/src/index.ts
added: providers/resend/src/lib/repend.provider.ts
added: providers/resend/src/lib/repend.provider.spec.ts
added: providers/resend/tsconfig.json
added: providers/resend/tsconfig.module.json
```

These files are located in the `/providers/resend` directory. Navigate to `/providers/resend/src/lib/resend.provider.ts` in your editor and you should see a template code to start from:

```javascript
import {
  ChannelTypeEnum,
  ISendMessageSuccessResponse,
  IEmailOptions,
  IEmailProvider,
} from '@novu/stateless';

export class ResendEmailProvider implements IEmailProvider {
  channelType = ChannelTypeEnum.EMAIL as ChannelTypeEnum.EMAIL;

  constructor(
    private config: {
      apiKey: string;
    }
  ) {
  }

  async sendMessage(
    options: IEmailOptions
  ): Promise<ISendMessageSuccessResponse> {


    return {
      id: 'PLACEHOLDER',
      date: 'PLACEHOLDER'
    };
  }
}
```
_/providers/resend/src/lib/resend.provider.ts_



### Write Code To Send Email

Thankfully, we have a great template to start. We'll proceed to write code.

> **Note:** I already signed up on Resend & have access to my api key. This is the time to set up an account on your Email provider platform if you have not done so.

I have added the complete code needed below to send emails in this file.

```javascript
import {
  ChannelTypeEnum,
  ISendMessageSuccessResponse,
  ICheckIntegrationResponse,
  CheckIntegrationResponseEnum,
  IEmailOptions,
  IEmailProvider,
} from '@novu/stateless';
import { Resend } from 'resend';

export class ResendEmailProvider implements IEmailProvider {
  id = 'resend';
  channelType = ChannelTypeEnum.EMAIL as ChannelTypeEnum.EMAIL;
  private resendClient: Resend;

  constructor(
    private config: {
      apiKey: string;
      from: string;
    }
  ) {
    this.resendClient = new Resend(this.config.apiKey);
  }

  async sendMessage(
    options: IEmailOptions
  ): Promise<ISendMessageSuccessResponse> {
    const response: any = await this.resendClient.sendEmail({
      from: options.from || this.config.from,
      to: options.to,
      subject: options.subject,
      text: options.text,
      html: options.html,
      cc: options.cc,
      attachments: options.attachments?.map((attachment) => ({
        filename: attachment?.name,
        content: attachment.file,
      })),
      bcc: options.bcc,
    });

    return {
      id: response.id,
      date: new Date().toISOString(),
    };
  }

  async checkIntegration(
    options: IEmailOptions
  ): Promise<ICheckIntegrationResponse> {
    try {
      await this.resendClient.sendEmail({
        from: options.from || this.config.from,
        to: options.to,
        subject: options.subject,
        text: options.text,
        html: options.html,
        cc: options.cc,
        attachments: options.attachments?.map((attachment) => ({
          filename: attachment?.name,
          content: attachment.file,
        })),
        bcc: options.bcc,
      });

      return {
        success: true,
        message: 'Integrated successfully!',
        code: CheckIntegrationResponseEnum.SUCCESS,
      };
    } catch (error) {
      return {
        success: false,
        message: error?.message,
        code: CheckIntegrationResponseEnum.FAILED,
      };
    }
  }
}
```

Here's a breakdown of the code above:

- Import resend from the resend library. 
```
import { Resend } from 'resend';
```

**Note:** Ensure you install the email API client library in the root of the email provider folder. In this case: `/apps/providers/resend`, **NOT THE ROOT OF THE NOVU PROJECT.**

- Set `id = 'resend'`. This is an identifier for Novu to recognise this provider in other parts of the project.

- Initialize the email client and set it in the constructor to accept the api key.

```javascript
...
private resendClient: Resend;

  constructor(
    private config: {
      apiKey: string;
      from: string;
    }
  ) {
    this.resendClient = new Resend(this.config.apiKey);
  }
...
```

- The `sendMessage` method contains the code needed to send the email and return a response.

- The `checkIntegration` method is almost a copy of the `sendMessage` method. It's needed for testing that the email integration works.

### Add Email Provider Logos

We need the new email provider to show up in the Integration store on Novu's dashboard. The dark and light mode logos should be present.

Head over to `apps/web/public/static/images/providers/dark` and `apps/web/public/static/images/providers/light` directories and the dark and light mode logos respectively. The name of the logos should be name of the provider. The possible formats are **svg** and **png**.

In my case, I added **resend.svg**.

![Light Mode](https://res.cloudinary.com/unicodeveloper/image/upload/v1677084064/resend_lightmode_l0ysxl.png)
_apps/web/public/static/images/providers/light/resend.svg_

![Dark Mode](https://res.cloudinary.com/unicodeveloper/image/upload/v1677084064/resend_darkmode_pe3a9p.png)
_apps/web/public/static/images/providers/dark/resend.svg_


### Build the UI Integration Store

Don't fret, we're not writing any CSS. :smile: 

We need to provide Novu with some details about our new email provider for it to build the UI Integration store. Two parts:

- Create credentials config
- Add provider configuration to providers list

**Create credentials config:**

We need to add the credentials that are needed in order to create integration with the provider. Add a config object in `libs/shared/src/consts/providers/credentials/provider-credentials.ts` like below:

```javascript
export const resendConfig: IConfigCredentials[] = [
  {
    key: CredentialsKeyEnum.ApiKey,
    displayName: 'API Key',
    type: 'string',
    required: true,
  },
  ...mailConfigBase,
];
```
_provider-credentials.ts_

**Add Provider Configuration to Providers list:**

Add the new email provider data to the list in `libs/shared/src/consts/providers/channels/email.ts`. 

> **Note:** The **id** is the provider's name, **displayName** is the provider's name in pascal case, **credentials** are the one you created in the previous step, **logoFileName** should be as it was in the adding logo step (with the format type included).

```javascript`

import {
  ...
  resendConfig,
} from '../credentials';

export const emailProviders: IProviderConfig[] = [
...
...
{
    id: EmailProviderIdEnum.Resend,
    displayName: 'Resend',
    channel: ChannelTypeEnum.EMAIL,
    credentials: resendConfig,
    docReference: 'https://resend.com/docs',
    logoFileName: { light: 'resend.svg', dark: 'resend.svg' },
}];
```
`libs/shared/src/consts/providers/channels/email.ts`

### Add Provider handler in the API

Navigate to `apps/api` directory.

- We'll start by **adding the provider dependency to the API**.

In the step where we ran the command to create the template, the project created a standalone provider package that will be published to NPM. In our development environment, it is not yet published. 

In order to use it locally, head over to the package.json located in `apps/api/package.json` and add it manually to the dependencies list: "@novu/<NEW_PROVIDER_NAME>": "^VERSION"

```javascript
"@novu/resend": "^0.11.0"
```
_apps/api/package.json_

The value of **VERSION** should be the package.json version number at the time.

Once you are done, run the `npm build` command to take into consideration the change we just made!

- Next, we'll **create the provider handler in the API**.

In order to map internally the different providers credentials, we need to add a provider handler that is located in the `apps/api/src/app/events/services/mail-service/handlers` directory.

Create a `resend.handler.ts` file in this directory and add the following code to it.

```javascript
import { ChannelTypeEnum } from '@novu/shared';
import { ICredentials } from '@novu/dal';
import { ResendEmailProvider } from '@novu/resend';
import { BaseHandler } from './base.handler';

export class ResendHandler extends BaseHandler {
  constructor() {
    super('resend', ChannelTypeEnum.EMAIL);
  }
  buildProvider(credentials: ICredentials, from?: string) {
    const config: { apiKey: string; from: string } = { from: from as string, apiKey: credentials.apiKey as string };

    this.provider = new ResendEmailProvider(config);
  }
}
```
_resend.handler.ts_

- Export the recently recreated handler in the `index.ts` file (still in the handlers directory)

```javascript
...
export * from './resend.handler';
```
_handlers/index.ts_

- Finally, we'll **add the handler to the factory**.

The factory is located in `apps/api/src/app/events/services/mail-service/mail.factory.ts`.

Add the Resend Handler like so:

```javascript
import { IntegrationEntity } from '@novu/dal';
import {
  ...
  ...
  ResendHandler,
} from './handlers';

import { IMailHandler } from './interfaces/send.handler.interface';

export class MailFactory {
  handlers: IMailHandler[] = [
    ...
    ...
    new ResendHandler(),
  ];

  ...
}
```
_apps/api/src/app/events/services/mail-service/mail.factory.ts_

## Build & Run Novu

To run the project successfully, build from the root of the Novu directory:

```javascript
pnpm build
```

- Navigate to `/apps/api` and run “pnpm start” in one terminal
- Navigate to `/apps/web` and run “pnpm start” in another terminal.

You should be able to login, activate the new email provider and connect successfully.

## A Call To Contribute to Open Source

Here's [my PR to add Resend to Novu](https://github.com/novuhq/novu/pull/2802).

Personally, open source has changed my life and career tremendously. So, if you are:

- Looking for ways to hone your skills,
- Looking for your first OSS project,
- Experienced and want to do something meaningful in OSS,
- Want to contribute to OSS

..then I recommend you contribute to [Novu](https://github.com/novuhq/novu).

## Conclusion

I hope you enjoyed following this guide as much as I enjoyed writing it. It covers everything you need to know to add email providers to Novu. This flow can also be adopted to add SMS & Push notification providers. 

> Novu provides the simplest and easiest way to help developers manage multi-channel notifications with a single API.

One more thing. I'll like to know how you're currently handling sending notifications via multiple channels to your users. Please, let me know in the comments section below!