SolidX
ReferenceExtending SolidXBackend Customization

Email Providers

Learn how to create and configure custom email providers in SolidX.

Overview

SolidX provides an SMTP email provider out of the box, which supports sending emails both synchronously and asynchronously (via background jobs).
However, there may be cases where you need to create your own provider - for example, to send emails using a third‑party API service.

To make this easy, SolidX offers abstractions that help you implement your own provider while keeping the code clean, testable, and decoupled.

Steps to Create a Custom Email Provider

1. Implement a Custom Email Service

Below is a sample implementation of a custom email provider using a hypothetical API‑based service.

It implements the IMail interface and is decorated with @MailProvider(). It uses EmailTemplateService for template handling and PublisherFactory for queued delivery.

This class supports sending email both with and without templates, synchronously or asynchronously. Put your custom email sending logic, such as API calls, in sendEmailSynchronously(). The rest of the class handles queueing and template rendering, which can be reused across providers.

import { HttpService } from "@nestjs/axios";
import { Inject, Injectable, Logger } from "@nestjs/common";
import { ConfigType } from "@nestjs/config";
import { 
  commonConfig, 
  EmailTemplateService, 
  IMail, 
  MailAttachment, 
  MailProvider, 
  PublisherFactory, 
  QueueMessage 
} from "@solidxai/core";
import * as Handlebars from "handlebars";

@Injectable()
@MailProvider()
export class CustomNotifyApiEmailService implements IMail {
  private readonly logger = new Logger(this.constructor.name);
  private readonly EMAIL_NOTIFICATION_TYPE = "email";
  private readonly DEFAULT_REQUEST_SERVICE = "custom"; 

  constructor(
    @Inject(commonConfig.KEY)
    private readonly commonConfiguration: ConfigType<typeof commonConfig>,
    private readonly publisherFactory: PublisherFactory<any>,
    private readonly emailTemplateService: EmailTemplateService,
    private readonly httpService: HttpService
  ) {}

  async sendEmailUsingTemplate(
    to: string,
    templateName: string,
    templateParams: any,
    shouldQueueEmails: boolean,
    wrapperAttachments?: any[],
    attachments?: MailAttachment[],
    parentEntity?: any,
    parentEntityId?: any
  ): Promise<void> {
    const emailTemplate = await this.emailTemplateService.findOneByName(templateName);
    if (!emailTemplate) {
      throw new Error(`Invalid template name ${templateName}`);
    }

    const bodyTemplate = Handlebars.compile(emailTemplate.body);
    const body = bodyTemplate(templateParams);

    const subjectTemplate = Handlebars.compile(emailTemplate.subject);
    const subject = subjectTemplate(templateParams);

    await this.sendEmail(to, subject, body, shouldQueueEmails, parentEntity, parentEntityId, attachments);
  }

  async sendEmail(
    to: string,
    subject: string,
    body: string,
    shouldQueueEmails: boolean,
    wrapperAttachments?: any[],
    attachments?: MailAttachment[],
    parentEntity?: any,
    parentEntityId?: any
  ): Promise<void> {
    const message = {
      payload: {
        from: this.commonConfiguration.apiMail.from,
        to,
        subject,
        body,
        attachments,
      },
    };

    if (shouldQueueEmails === true) {
      this.sendEmailAsynchronously(message);
    } else if (shouldQueueEmails == false && this.commonConfiguration.shouldQueueEmails === true) {
      this.sendEmailAsynchronously(message);
    } else {
      await this.sendEmailSynchronously(message);
    }
  }

  async sendEmailAsynchronously(message: QueueMessage<any>): Promise<void> {
    const { to, subject, body } = message.payload;

    this.publisherFactory.publish(message, "CustomNotifyApiEmailQueuePublisher");

    this.logger.verbose(`Queueing email to ${to} with subject ${subject} and body ${body}`);
  }

  async sendEmailSynchronously(message: QueueMessage<any>): Promise<void> {
    const { from, to, subject, body } = message.payload;

    this.logger.debug(`Pretending to send email synchronously via external API...`);
    this.logger.debug(`From: ${from}, To: ${to}, Subject: ${subject}`);
    this.logger.debug(`Body: ${body}`);

    // Example: simulate API call
    // await this.httpService.axiosRef.post("https://dummy-email-api.com/send", {
    //   sender: from,
    //   receiver: [to],
    //   subject,
    //   body,
    //   notification_type: this.EMAIL_NOTIFICATION_TYPE,
    //   request_service: this.DEFAULT_REQUEST_SERVICE,
    // });
  }
}

2. Register Your Custom Provider

app.module.ts
import { Module } from "@nestjs/common";
import { CustomNotifyApiEmailService } from "./custom-notify-api-email.service";

@Module({
  providers: [CustomNotifyApiEmailService], // register your custom provider
})
export class AppModule {}

3. Use the MailFactory to Send Emails

You can now use MailFactory to send emails either via templates or manually.

Example: Using an Email Template

import { MailFactory } from "@solidxai/core";   
...
constructor(private readonly mailFactory: MailFactory) {}
...
const mailService = this.mailFactory.getMailService();
await mailService.sendEmailUsingTemplate(
  user.email,
  "forgot-password",
  {
    solidAppName: process.env.SOLID_APP_NAME,
    firstName: user.firstName,
  },
  this.commonConfiguration.shouldQueueEmails,
  null,
  null,
  "user",
  user.id
);

Example: Sending a Manual Email (Without Template)

import { MailFactory } from "@solidxai/core";   
...
constructor(private readonly mailFactory: MailFactory) {}
...
const mailService = this.mailFactory.getMailService();
await mailService.sendEmail(
  user.email, // to
  "Welcome to SolidX!", // subject
  "Hello John, your account has been successfully created!", // body
  false // send synchronously
);

Interface Definition

The IMail interface defines two main methods your provider must implement.

export interface IMail<TResponse = unknown> {
  sendEmail(
    to: string,
    subject: string,
    body: string,
    shouldQueueEmails: boolean,
    wrapperAttachments?: MailAttachmentWrapper[],
    attachments?: MailAttachment[],
    parentEntity?: any,
    parentEntityId?: any,
    cc?: string[],
    bcc?: string[],
    from?: string,
  ): Promise<TResponse>;

  sendEmailUsingTemplate(
    to: string,
    templateName: string,
    templateParams: any,
    shouldQueueEmails: boolean,
    wrapperAttachments?: MailAttachmentWrapper[],
    attachments?: MailAttachment[],
    parentEntity?: any,
    parentEntityId?: any,
    cc?: string[],
    bcc?: string[],
    from?: string,
  ): Promise<TResponse>;
}

Recommendation

Use email templates for better separation of content and logic. It allows you to modify templates without changing your backend code.

Summary

StepDescription
1Create a class implementing IMail
2Decorate it with @MailProvider()
3Register it in your module
4Use MailFactory to send emails (template or manual)