Creating a Custom Mailer in the Next.js Supabase Starter Kit
Learn how to create a custom mailer in the Next.js Supabase Starter Kit, so you can send emails using your own email provider.
Makerkit implements both Nodemailer (which can potentially support any SMTP provider) and Resend, using its HTTP API, which allows Makerkit to be compatible with edge runtimes (such as Cloudflare Workers).
However, if you want to use your own email provider, you can create a custom mailer using the packages/mailers
package.
The mailer class is very simple:
export abstract class Mailer<Res = unknown> { abstract sendEmail(data: z.infer<typeof MailerSchema>): Promise<Res>;}
As such, you can create your own mailer by extending the Mailer
class and implementing the sendEmail
method.
Implementing the sendEmail method
The sendEmail
method takes in a z.infer<typeof MailerSchema>
object, which is a Zod schema that defines the shape of the email data. This allows you to validate the email data before sending it.
Here's an example of how to implement the sendEmail
method. For simplicity, we will call our custom mailer megamailer
in the following examples.
import { z } from 'zod';import { MailerSchema } from '../../shared/src/schema/mailer.schema';export function createMegaMailer() { return new MegaMailer();}class MegaMailer implements Mailer<unknown> { async sendEmail(data: z.infer<typeof MailerSchema>) { // Implement your email sending logic here // For example, you can use Nodemailer to send emails // or use a third-party email provider like SendGrid return {}; }}
NB: for simplicity, we've placed the megamailer.ts
file in the core package. However, if you have 5 minutes, you can move it to a separate package and import it in the index.ts
file, just like we do for Nodemailer and Resend.
Now that we have our custom mailer, we need to register it in the packages/mailers/core/src/index.ts
file:
import { z } from 'zod';const MAILER_PROVIDER = z .enum(['nodemailer', 'resend', 'megamailer']) .default('nodemailer') .parse(process.env.MAILER_PROVIDER);/** * @description Get the mailer based on the environment variable. */export async function getMailer() { switch (MAILER_PROVIDER) { case 'nodemailer': return getNodemailer(); case 'resend': { const { createResendMailer } = await import('@kit/resend'); return createResendMailer(); } case 'megamailer': { const { createMegamailer } = await import('./megamailer'); return createMegamailer(); } default: throw new Error(`Invalid mailer: ${MAILER_PROVIDER as string}`); }}async function getNodemailer() { if (process.env.NEXT_RUNTIME === 'nodejs') { const { createNodemailerService } = await import('@kit/nodemailer'); return createNodemailerService(); } else { throw new Error( 'Nodemailer is not available on the edge runtime. Please use another mailer.', ); }}
By setting the MAILER_PROVIDER
environment variable to megamailer
, you can use the Megamailer mailer in your application.
MAILER_PROVIDER=megamailer
Moving Megamailer to a separate package
Let's move the Megamailer to a separate package. To do so, we create a new package called @kit/megamailer
and move the megamailer.ts
file to the new package.
Let's create a package at packages/mailers/megamailer
.
- Copy both the
package.json
andtsconfig.json
files from theresend
package to the new package and rename the package to@kit/megamailer
- Copy the
megamailer.ts
file to the new package - Update the
index.ts
file to export the new package and use the Megamailer
Install the Megamailer package
To install the new package, run the following command:
pnpm i "@kit/megamailer:workspace:*" --filter "@kit/mailers"
Update the index.ts
file
Now that we have the new package, we need to update the index.ts
file to export the new package and use the Megamailer.
import { z } from 'zod';const MAILER_PROVIDER = z .enum(['nodemailer', 'resend', 'megamailer']) .default('nodemailer') .parse(process.env.MAILER_PROVIDER);/** * @description Get the mailer based on the environment variable. */export async function getMailer() { switch (MAILER_PROVIDER) { case 'nodemailer': return getNodemailer(); case 'resend': { const { createResendMailer } = await import('@kit/resend'); return createResendMailer(); } case 'megamailer': { const { createMegamailer } = await import('@kit/megamailer'); return createMegamailer(); } default: throw new Error(`Invalid mailer: ${MAILER_PROVIDER as string}`); }}async function getNodemailer() { if (process.env.NEXT_RUNTIME === 'nodejs') { const { createNodemailerService } = await import('@kit/nodemailer'); return createNodemailerService(); } else { throw new Error( 'Nodemailer is not available on the edge runtime. Please use another mailer.', ); }}