Presentation
Sending emails from a backend is a pretty common feature to have, so we, at Flexper, decided to make a library to simplify how to implement it inside our NodeJS applications.
In this article, we will present and use sendim, our custom abstraction to wrap any email providers to send raw and transactional emails to your clients.
We have already made two adapters for the most common providers, sendim-mandrill and sendim-sendinblue
Having the same interface for sending emails no matter the provider lets us easily swap between one another inside a project but also unifies how we are sending emails throughout every project.
General use
As we said, the point of this library is to unify how to use email providers with a general interface.
From here, no matter what adapter you will use, it will always looks the same
npm i sendim
Adapters
We have already made some adapters for the email providers we use.
We will see how to create your own adapters further down.
Mandrill adapter
Here is how the configuration for using Mandrill as a provider looks like
npm i sendim-mandrill
import { Sendim } from 'sendim';
import { SendimMandrillProviderConfig, SendimMandrillProvider } from 'sendim-mandrill';
const sendim = new Sendim();
await sendim.addTransport<SendimMandrillProviderConfig>(
SendimMandrillProvider,
{ apiKey: process.env.MANDRILL_APIKEY! }, // You need to have this environment variable set in a .env file
);
Sendinblue adapter
And now look how similar the Sendinblue adapter is
npm i sendim-sendinblue
import { Sendim } from 'sendim';
import { SendimSendinblueProviderConfig, SendimSendinblueProvider } from 'sendim-sendinblue';
const sendim = new Sendim();
await sendim.addTransport<SendimSendinblueProviderConfig>(
SendimSendinblueProvider,
{ apiKey: process.env.SENDINBLUE_APIKEY! }, // You need to have this environment variable set in a .env file
);
You can add multiple adapters to the same 'Sendim' instance.
Sending emails
Strongly typed interface
Sendim is using very simple yet exhaustive interfaces in TypeScript in order to handle emailing.
Whether for sending transactional or raw emails, you'll be using the same general typing
type MailOptions = {
to: EmailInformation[];
cc?: EmailInformation[];
bcc?: EmailInformation[];
sender: EmailInformation;
reply?: EmailInformation;
attachments?: EmailAttachment[];
};
interface EmailInformation {
email: string;
name?: string;
}
interface EmailAttachment {
name: string;
contentType: string;
content: string;
}
Even tho you can add multiple transports (adapters) to the same Sendim instance, by default, only the first added one will be used. But you can specify a name as second argument of the commands below to target another adapter ('mandrill' or 'sendinblue').
Raw emails
await sendim.sendRawMail({
// Common commands attributes
to: [
{
email: 'test@test.fr',
},
],
sender: {
email: 'test@test.fr',
},
// Raw mail attributes
subject: 'How great is Sendim',
htmlContent: '<b>Very much !</b>'
});
Instead of the htmlContent attribute you could use the textContent one
Transactional emails
await sendim.sendTransactionalMail({
// Common commands attributes
to: [
{
email: 'test@test.fr',
},
],
sender: {
email: 'test@test.fr',
},
// Transactional mail attributes
templateId: '6', // The template id/slug from your email provider
params: { // An object to fill the variables in your template
akey: 'My first value',
anotherKey: 'My second value',
},
});
Create your own adapter
Here we will see the bases to create a Sendim adapter.
import {
RawMailOptions,
SendimTransportInterface,
TransactionalMailOptions,
} from 'sendim';
// That's the interface for how to initialize your adapter
export interface MyProviderConfig {
apiKey: string;
}
// This is the adapter that you will add as a transport to yor future Sendim instance
export class MyProvider implements SendimTransportInterface {
// You have to provide a name in order to be able to target it if your app is using multiple transports
providerName = 'my-provider';
constructor(public config: MyProviderConfig) {
// Use your configuration interface to connect to your email provider: set class attributes, do requests ...
// ...
}
async isHealthy() {
// You need to provide a way to check whether or not the connection to your provider is working
}
async sendRawMail({ attachments, ...options }: RawMailOptions) {
// Implement the logic of your provider given the RawMailOptions interface common to every transports
}
async sendTransactionalMail({
attachments,
...options
}: TransactionalMailOptions) {
// Implement the logic of your provider given the TransactionalMailOptions interface common to every transports
}
}
Conclusion
The Sendim library and its providers are a convenient way to deal with email without having to pay too much attention to the provider's implementation; Once abstracted in a Sendim adapter, you can have the same interface throughout your projects and easily switch them making your code more flexible
Feel free to submit issues on the GitHub if you're having any trouble and happy coding!