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.
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 or manually integrate dozens of communication APIs in your app.
Get Started
Head over to GitHub to clone the Novu repo. Feel free to star the repo while you are at it. 😉
Next, create a new branch from the next
branch.
git checkout -b add-resend-email-provider next
Install the packages in the Novu project:
npm install
Set Up New Email Provider
Run the command below to seamlessly create the templates needed to add a new email provider.
npm run generate:provider
It generates a prompt like so:
> 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:
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.
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.
...
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 thesendMessage
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.
apps/web/public/static/images/providers/light/resend.svg
apps/web/public/static/images/providers/dark/resend.svg
Build the UI Integration Store
Don't fret, we're not writing any CSS. 😄
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:
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).
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/": "^VERSION"
"@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.
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)
...
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:
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:
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.
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.
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!