Refactor mailer setup and validate web configuration in app
This commit refactors how SMTP configuration for mailers is set up and introduces schema validation for incoming configurations. The mailer modules have been restructured, with schema definition files added, and redundant codes removed. Moreover, web application configuration now has minimum validation on name and title, and URL validation has been added.
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "0.2.0",
|
||||
"@kit/prettier-config": "0.1.0",
|
||||
"@kit/shared": "0.1.0",
|
||||
"@kit/supabase": "0.1.0",
|
||||
"@kit/tailwind-config": "0.1.0",
|
||||
"@kit/tsconfig": "0.1.0",
|
||||
@@ -25,7 +26,8 @@
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@tanstack/react-query": "5.28.6",
|
||||
"react-i18next": "^14.1.0",
|
||||
"sonner": "^1.4.41"
|
||||
"sonner": "^1.4.41",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
"eslintConfig": {
|
||||
|
||||
@@ -4,8 +4,7 @@ import { useRouter, useSearchParams } from 'next/navigation';
|
||||
|
||||
import type { Provider } from '@supabase/supabase-js';
|
||||
|
||||
import { isBrowser } from '@supabase/ssr';
|
||||
|
||||
import { isBrowser } from '@kit/shared/utils';
|
||||
import { Divider } from '@kit/ui/divider';
|
||||
import { If } from '@kit/ui/if';
|
||||
|
||||
|
||||
17
packages/mailers/src/impl/cloudflare/index.ts
Normal file
17
packages/mailers/src/impl/cloudflare/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'server-only';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Mailer } from '../../mailer';
|
||||
import { MailerSchema } from '../../schema/mailer.schema';
|
||||
|
||||
type Config = z.infer<typeof MailerSchema>;
|
||||
|
||||
/**
|
||||
* A class representing a mailer using Cloudflare's Workers.
|
||||
* @implements {Mailer}
|
||||
*/
|
||||
export class CloudflareMailer implements Mailer {
|
||||
async sendEmail(config: Config) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import 'server-only';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Mailer } from '../mailer';
|
||||
import { MailerSchema } from '../mailer-schema';
|
||||
|
||||
type Config = z.infer<typeof MailerSchema>;
|
||||
|
||||
/**
|
||||
* A class representing a mailer using Nodemailer library.
|
||||
* @implements {Mailer}
|
||||
*/
|
||||
export class Nodemailer implements Mailer {
|
||||
async sendEmail(config: Config) {
|
||||
const transporter = await getSMTPTransporter();
|
||||
|
||||
return transporter.sendMail(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description SMTP Transporter for production use. Add your favorite email
|
||||
* API details (Mailgun, Sendgrid, etc.) to the appConfig.
|
||||
*/
|
||||
async function getSMTPTransporter() {
|
||||
const { createTransport } = await import('nodemailer');
|
||||
|
||||
return createTransport(getSMTPConfiguration());
|
||||
}
|
||||
|
||||
function getSMTPConfiguration() {
|
||||
const user = process.env.EMAIL_USER;
|
||||
const pass = process.env.EMAIL_PASSWORD;
|
||||
const host = process.env.EMAIL_HOST;
|
||||
const port = Number(process.env.EMAIL_PORT);
|
||||
const secure = process.env.EMAIL_TLS !== 'false';
|
||||
|
||||
// validate that we have all the required appConfig
|
||||
if (!user || !pass || !host || !port) {
|
||||
throw new Error(
|
||||
`Missing email configuration. Please add the following environment variables:
|
||||
EMAIL_USER
|
||||
EMAIL_PASSWORD
|
||||
EMAIL_HOST
|
||||
EMAIL_PORT
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
host,
|
||||
port,
|
||||
secure,
|
||||
auth: {
|
||||
user,
|
||||
pass,
|
||||
},
|
||||
};
|
||||
}
|
||||
21
packages/mailers/src/impl/nodemailer/index.ts
Normal file
21
packages/mailers/src/impl/nodemailer/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'server-only';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Mailer } from '../../mailer';
|
||||
import { MailerSchema } from '../../schema/mailer.schema';
|
||||
import { getSMTPConfiguration } from '../../smtp-configuration';
|
||||
|
||||
type Config = z.infer<typeof MailerSchema>;
|
||||
|
||||
/**
|
||||
* A class representing a mailer using Nodemailer library.
|
||||
* @implements {Mailer}
|
||||
*/
|
||||
export class Nodemailer implements Mailer {
|
||||
async sendEmail(config: Config) {
|
||||
const { createTransport } = await import('nodemailer');
|
||||
const transporter = createTransport(getSMTPConfiguration());
|
||||
|
||||
return transporter.sendMail(config);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { MailerSchema } from './mailer-schema';
|
||||
import { MailerSchema } from './schema/mailer.schema';
|
||||
|
||||
export abstract class Mailer<Res = unknown> {
|
||||
abstract sendEmail(data: z.infer<typeof MailerSchema>): Promise<Res>;
|
||||
|
||||
49
packages/mailers/src/schema/smtp-config.schema.ts
Normal file
49
packages/mailers/src/schema/smtp-config.schema.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/*
|
||||
const user = process.env.EMAIL_USER;
|
||||
const pass = process.env.EMAIL_PASSWORD;
|
||||
const host = process.env.EMAIL_HOST;
|
||||
const port = Number(process.env.EMAIL_PORT);
|
||||
const secure = process.env.EMAIL_TLS !== 'false';
|
||||
|
||||
// validate that we have all the required appConfig
|
||||
if (!user || !pass || !host || !port) {
|
||||
throw new Error(
|
||||
`Missing email configuration. Please add the following environment variables:
|
||||
EMAIL_USER
|
||||
EMAIL_PASSWORD
|
||||
EMAIL_HOST
|
||||
EMAIL_PORT
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
host,
|
||||
port,
|
||||
secure,
|
||||
auth: {
|
||||
user,
|
||||
pass,
|
||||
},
|
||||
};
|
||||
*/
|
||||
|
||||
export const SmtpConfigSchema = z.object({
|
||||
user: z.string({
|
||||
description:
|
||||
'This is the email account to send emails from. This is specific to the email provider.',
|
||||
}),
|
||||
pass: z.string({
|
||||
description: 'This is the password for the email account',
|
||||
}),
|
||||
host: z.string({
|
||||
description: 'This is the SMTP host for the email provider',
|
||||
}),
|
||||
port: z.number({
|
||||
description:
|
||||
'This is the port for the email provider. Normally 587 or 465.',
|
||||
}),
|
||||
secure: z.boolean(),
|
||||
});
|
||||
21
packages/mailers/src/smtp-configuration.ts
Normal file
21
packages/mailers/src/smtp-configuration.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { SmtpConfigSchema } from './schema/smtp-config.schema';
|
||||
|
||||
export function getSMTPConfiguration() {
|
||||
const data = SmtpConfigSchema.parse({
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASSWORD,
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: Number(process.env.EMAIL_PORT),
|
||||
secure: process.env.EMAIL_TLS !== 'false',
|
||||
});
|
||||
|
||||
return {
|
||||
host: data.host,
|
||||
port: data.port,
|
||||
secure: data.secure,
|
||||
auth: {
|
||||
user: data.user,
|
||||
pass: data.pass,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user