refactor: refactor mail module & enhance auth email with locale support
- Added locale handling to email sending functions, allowing for localized URLs in reset password and verification emails. - Introduced a new utility function `addLocaleToUrl` to append locale to callback URLs in authentication flows. - Refactored the email sending process to utilize a mail provider interface, supporting both templated and raw email sending. - Created a comprehensive README for the email system, detailing usage, configuration, and available templates. - Established a default mail configuration for improved email management.
This commit is contained in:
parent
6744c52087
commit
130338b9a5
@ -41,6 +41,7 @@ export const contactAction = actionClient
|
||||
|
||||
// Send email using the mail service
|
||||
// Customize the email template for your needs
|
||||
// TODO: add locale to the email or customize it by yourself?
|
||||
const result = await send({
|
||||
to: websiteConfig.mail.to,
|
||||
subject: `Contact Form: Message from ${name}`,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import db from '@/db/index';
|
||||
import { account, session, user, verification } from '@/db/schema';
|
||||
import { defaultMessages } from '@/i18n/messages';
|
||||
import { getLocaleFromRequest } from '@/lib/utils';
|
||||
import { getLocaleFromRequest, addLocaleToUrl } from '@/lib/utils';
|
||||
import { send } from '@/mail';
|
||||
import { betterAuth } from 'better-auth';
|
||||
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
|
||||
@ -48,12 +48,17 @@ export const auth = betterAuth({
|
||||
// https://www.better-auth.com/docs/authentication/email-password#forget-password
|
||||
async sendResetPassword({ user, url }, request) {
|
||||
const locale = getLocaleFromRequest(request);
|
||||
// TODO: add locale to url
|
||||
// console.log('[Auth] Reset password original URL:', url);
|
||||
|
||||
// Add locale to URL if necessary
|
||||
const localizedUrl = addLocaleToUrl(url, locale);
|
||||
// console.log('[Auth] Reset password localized URL:', localizedUrl);
|
||||
|
||||
await send({
|
||||
to: user.email,
|
||||
template: 'forgotPassword',
|
||||
context: {
|
||||
url,
|
||||
url: localizedUrl,
|
||||
name: user.name,
|
||||
},
|
||||
locale,
|
||||
@ -66,12 +71,17 @@ export const auth = betterAuth({
|
||||
// https://www.better-auth.com/docs/authentication/email-password#require-email-verification
|
||||
sendVerificationEmail: async ({ user, url, token }, request) => {
|
||||
const locale = getLocaleFromRequest(request);
|
||||
// TODO: add locale to url
|
||||
// console.log('[Auth] Verification email original URL:', url);
|
||||
|
||||
// Add locale to URL if necessary
|
||||
const localizedUrl = addLocaleToUrl(url, locale);
|
||||
// console.log('[Auth] Verification email localized URL:', localizedUrl);
|
||||
|
||||
await send({
|
||||
to: user.email,
|
||||
template: 'verifyEmail',
|
||||
context: {
|
||||
url,
|
||||
url: localizedUrl,
|
||||
name: user.name,
|
||||
},
|
||||
locale,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { LOCALE_COOKIE_NAME, routing } from '@/i18n/routing';
|
||||
import { shouldAppendLocale } from '@/lib/urls/get-base-url';
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { parse as parseCookies } from 'cookie';
|
||||
import { Locale } from 'next-intl';
|
||||
@ -75,3 +76,51 @@ export function estimateReadingTime(
|
||||
const minutes = Math.ceil(words / wordsPerMinute);
|
||||
return minutes === 1 ? '1 minute read' : `${minutes} minutes read`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds locale to the callbackURL parameter in authentication URLs
|
||||
*
|
||||
* Example:
|
||||
* Input: http://localhost:3000/api/auth/reset-password/token?callbackURL=/auth/reset-password
|
||||
* Output: http://localhost:3000/api/auth/reset-password/token?callbackURL=/zh/auth/reset-password
|
||||
*
|
||||
* http://localhost:3000/api/auth/verify-email?token=eyJhbGciOiJIUzI1NiJ9&callbackURL=/dashboard
|
||||
* Output: http://localhost:3000/api/auth/verify-email?token=eyJhbGciOiJIUzI1NiJ9&callbackURL=/zh/dashboard
|
||||
*
|
||||
* @param url - The original URL with callbackURL parameter
|
||||
* @param locale - The locale to add to the callbackURL
|
||||
* @returns The URL with locale added to callbackURL if necessary
|
||||
*/
|
||||
export function addLocaleToUrl(url: string, locale: Locale): string {
|
||||
// If we shouldn't append locale, return original URL
|
||||
if (!shouldAppendLocale(locale)) {
|
||||
return url;
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse the URL
|
||||
const urlObj = new URL(url);
|
||||
|
||||
// Check if there's a callbackURL parameter
|
||||
const callbackURL = urlObj.searchParams.get('callbackURL');
|
||||
|
||||
if (callbackURL) {
|
||||
// Only modify the callbackURL if it doesn't already include the locale
|
||||
if (!callbackURL.match(new RegExp(`^/${locale}(/|$)`))) {
|
||||
// Add locale to the callbackURL
|
||||
const localizedCallbackURL = callbackURL.startsWith('/')
|
||||
? `/${locale}${callbackURL}`
|
||||
: `/${locale}/${callbackURL}`;
|
||||
|
||||
// Update the search parameter
|
||||
urlObj.searchParams.set('callbackURL', localizedCallbackURL);
|
||||
}
|
||||
}
|
||||
|
||||
return urlObj.toString();
|
||||
} catch (e) {
|
||||
// If URL parsing fails, return the original URL
|
||||
console.warn('Failed to parse URL for locale insertion:', url, e);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
183
src/mail/README.md
Normal file
183
src/mail/README.md
Normal file
@ -0,0 +1,183 @@
|
||||
# Email System
|
||||
|
||||
This module provides email functionality for the application. It supports sending emails using templates or raw content through different email providers.
|
||||
|
||||
## Structure
|
||||
|
||||
The email system is designed with the following components:
|
||||
|
||||
- **Provider Interface**: A common interface for email providers
|
||||
- **Email Templates**: React-based email templates for different purposes
|
||||
- **Configuration**: Configuration for email defaults and settings
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import { send } from '@/mail';
|
||||
|
||||
// Send using a template
|
||||
await send({
|
||||
to: 'user@example.com',
|
||||
template: 'verifyEmail',
|
||||
context: {
|
||||
name: 'John Doe',
|
||||
url: 'https://example.com/verify?token=abc123',
|
||||
},
|
||||
locale: 'en', // Optional, defaults to config default locale
|
||||
});
|
||||
|
||||
// Send a raw email
|
||||
await send({
|
||||
to: 'user@example.com',
|
||||
subject: 'Welcome to our platform',
|
||||
html: '<h1>Hello!</h1><p>Welcome to our platform.</p>',
|
||||
text: 'Hello! Welcome to our platform.', // Optional
|
||||
});
|
||||
```
|
||||
|
||||
### Using the Mail Provider Directly
|
||||
|
||||
```typescript
|
||||
import { getMailProvider, sendTemplate, sendRawEmail } from '@/mail';
|
||||
|
||||
// Get the provider
|
||||
const provider = getMailProvider();
|
||||
|
||||
// Send template email
|
||||
const result = await sendTemplate({
|
||||
to: 'user@example.com',
|
||||
template: 'welcomeEmail',
|
||||
context: {
|
||||
name: 'John Doe',
|
||||
},
|
||||
});
|
||||
|
||||
// Check result
|
||||
if (result.success) {
|
||||
console.log('Email sent successfully!', result.messageId);
|
||||
} else {
|
||||
console.error('Failed to send email:', result.error);
|
||||
}
|
||||
|
||||
// Send raw email
|
||||
await sendRawEmail({
|
||||
to: 'user@example.com',
|
||||
subject: 'Raw email example',
|
||||
html: '<p>This is a raw email</p>',
|
||||
});
|
||||
```
|
||||
|
||||
## Email Templates
|
||||
|
||||
Email templates are React components stored in the `emails` directory. Each template has specific props and is rendered to HTML/text when sent.
|
||||
|
||||
### Available Templates
|
||||
|
||||
- `verifyEmail`: For email verification
|
||||
- `forgotPassword`: For password reset
|
||||
- `subscribeNewsletter`: For new user subscribed
|
||||
|
||||
### Creating a New Template
|
||||
|
||||
1. Create a React component in the `emails` directory
|
||||
2. Make sure it accepts `BaseEmailProps` plus any specific props
|
||||
3. Add it to the `EmailTemplates` export in `emails/index.ts`
|
||||
4. Add corresponding subject translations in the i18n messages
|
||||
|
||||
Example:
|
||||
|
||||
```tsx
|
||||
// emails/MyNewEmail.tsx
|
||||
import { BaseEmailProps } from '@/mail/types';
|
||||
import { Body, Container, Head, Html, Text } from '@react-email/components';
|
||||
|
||||
interface MyNewEmailProps extends BaseEmailProps {
|
||||
username: string;
|
||||
}
|
||||
|
||||
export default function MyNewEmail({ username, messages, locale }: MyNewEmailProps) {
|
||||
return (
|
||||
<Html lang={locale}>
|
||||
<Head />
|
||||
<Body>
|
||||
<Container>
|
||||
<Text>Hello {username}!</Text>
|
||||
</Container>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Then add it to `emails/index.ts`:
|
||||
|
||||
```typescript
|
||||
import MyNewEmail from './MyNewEmail';
|
||||
|
||||
export const EmailTemplates = {
|
||||
// ... existing templates
|
||||
myNewEmail: MyNewEmail,
|
||||
};
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The email system configuration is defined in `config/mail-config.ts`. It includes settings like:
|
||||
|
||||
- Default "from" email address
|
||||
- Default locale for emails
|
||||
|
||||
## Providers
|
||||
|
||||
### Resend
|
||||
|
||||
[Resend](https://resend.com/) is the default email provider. It requires an API key set as `RESEND_API_KEY` in your environment variables.
|
||||
|
||||
### Adding a New Provider
|
||||
|
||||
To add a new email provider:
|
||||
|
||||
1. Create a new file in the `provider` directory
|
||||
2. Implement the `MailProvider` interface
|
||||
3. Update the `initializeMailProvider` function in `index.ts` to use your new provider
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
// provider/my-provider.ts
|
||||
import { MailProvider, SendEmailResult, SendRawEmailParams, SendTemplateParams } from '@/mail/types';
|
||||
|
||||
export class MyProvider implements MailProvider {
|
||||
constructor() {
|
||||
// Initialize your provider
|
||||
}
|
||||
|
||||
public async sendTemplate(params: SendTemplateParams): Promise<SendEmailResult> {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
public async sendRawEmail(params: SendRawEmailParams): Promise<SendEmailResult> {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
public getProviderName(): string {
|
||||
return 'my-provider';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then update `index.ts`:
|
||||
|
||||
```typescript
|
||||
import { MyProvider } from './provider/my-provider';
|
||||
|
||||
export const initializeMailProvider = (): MailProvider => {
|
||||
if (!mailProvider) {
|
||||
// Select provider based on configuration or environment
|
||||
mailProvider = new MyProvider();
|
||||
}
|
||||
return mailProvider;
|
||||
};
|
||||
```
|
11
src/mail/config/mail-config.ts
Normal file
11
src/mail/config/mail-config.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { websiteConfig } from '@/config';
|
||||
import { routing } from '@/i18n/routing';
|
||||
import { MailConfig } from '@/mail/types';
|
||||
|
||||
/**
|
||||
* Default mail configuration
|
||||
*/
|
||||
export const mailConfig: MailConfig = {
|
||||
defaultFromEmail: websiteConfig.mail.from || 'noreply@example.com',
|
||||
defaultLocale: routing.defaultLocale,
|
||||
};
|
@ -1,5 +1,74 @@
|
||||
// Export the send function for direct import
|
||||
export { send } from './mail';
|
||||
import { MailProvider, MailConfig, SendTemplateParams, SendRawEmailParams, SendEmailResult, Template } from './types';
|
||||
import { ResendProvider } from './provider/resend';
|
||||
import { mailConfig } from './config/mail-config';
|
||||
|
||||
// Export mail templates
|
||||
/**
|
||||
* Default mail configuration
|
||||
*/
|
||||
export const defaultMailConfig: MailConfig = mailConfig;
|
||||
|
||||
/**
|
||||
* Global mail provider instance
|
||||
*/
|
||||
let mailProvider: MailProvider | null = null;
|
||||
|
||||
/**
|
||||
* Initialize the mail provider
|
||||
* @returns initialized mail provider
|
||||
*/
|
||||
export const initializeMailProvider = (): MailProvider => {
|
||||
if (!mailProvider) {
|
||||
mailProvider = new ResendProvider();
|
||||
}
|
||||
return mailProvider;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the mail provider
|
||||
* @returns current mail provider instance
|
||||
* @throws Error if provider is not initialized
|
||||
*/
|
||||
export const getMailProvider = (): MailProvider => {
|
||||
if (!mailProvider) {
|
||||
return initializeMailProvider();
|
||||
}
|
||||
return mailProvider;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send an email using a template
|
||||
* @param params Parameters for sending the templated email
|
||||
* @returns Send result
|
||||
*/
|
||||
export const sendTemplate = async (params: SendTemplateParams):
|
||||
Promise<SendEmailResult> => {
|
||||
const provider = getMailProvider();
|
||||
return provider.sendTemplate(params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a raw email
|
||||
* @param params Parameters for sending the raw email
|
||||
* @returns Send result
|
||||
*/
|
||||
export const sendRawEmail = async (params: SendRawEmailParams):
|
||||
Promise<SendEmailResult> => {
|
||||
const provider = getMailProvider();
|
||||
return provider.sendRawEmail(params);
|
||||
};
|
||||
|
||||
// Export from mail.ts
|
||||
export { send, getTemplate } from './mail';
|
||||
|
||||
// Export email templates
|
||||
export { EmailTemplates } from './emails';
|
||||
|
||||
// Export types for convenience
|
||||
export type {
|
||||
MailProvider,
|
||||
MailConfig,
|
||||
SendTemplateParams,
|
||||
SendRawEmailParams,
|
||||
SendEmailResult,
|
||||
Template,
|
||||
};
|
||||
|
@ -1,89 +1,44 @@
|
||||
import { getMessagesForLocale } from '@/i18n/messages';
|
||||
import { routing } from '@/i18n/routing';
|
||||
import { getMailProvider } from '@/mail';
|
||||
import { EmailTemplates } from '@/mail/emails';
|
||||
import { sendEmail } from '@/mail/provider/resend';
|
||||
import { SendRawEmailParams, SendTemplateParams, Template } from '@/mail/types';
|
||||
import { render } from '@react-email/render';
|
||||
import { Locale, Messages } from 'next-intl';
|
||||
import { Template } from '@/mail/types';
|
||||
|
||||
/**
|
||||
* send email
|
||||
*
|
||||
* 1. with given template, and context
|
||||
* 2. with given subject, text, and html
|
||||
* Send email using the configured mail provider
|
||||
*
|
||||
* @param params Email parameters
|
||||
* @returns Success status
|
||||
*/
|
||||
export async function send<T extends Template>(
|
||||
params: {
|
||||
to: string;
|
||||
locale?: Locale;
|
||||
} & (
|
||||
| {
|
||||
template: T;
|
||||
context: Omit<
|
||||
Parameters<(typeof EmailTemplates)[T]>[0],
|
||||
'locale' | 'messages'
|
||||
>;
|
||||
}
|
||||
| {
|
||||
subject: string;
|
||||
text?: string;
|
||||
html?: string;
|
||||
}
|
||||
)
|
||||
export async function send(
|
||||
params: SendTemplateParams | SendRawEmailParams
|
||||
) {
|
||||
const { to, locale = routing.defaultLocale } = params;
|
||||
console.log('send, locale:', locale);
|
||||
const provider = getMailProvider();
|
||||
|
||||
let html: string;
|
||||
let text: string;
|
||||
let subject: string;
|
||||
|
||||
// if template is provided, get the template
|
||||
// otherwise, use the subject, text, and html
|
||||
if ('template' in params) {
|
||||
const { template, context } = params;
|
||||
const mailTemplate = await getTemplate({
|
||||
template,
|
||||
context,
|
||||
locale,
|
||||
});
|
||||
subject = mailTemplate.subject;
|
||||
text = mailTemplate.text;
|
||||
html = mailTemplate.html;
|
||||
// This is a template email
|
||||
const result = await provider.sendTemplate(params);
|
||||
return result.success;
|
||||
} else {
|
||||
subject = params.subject;
|
||||
text = params.text ?? '';
|
||||
html = params.html ?? '';
|
||||
}
|
||||
|
||||
try {
|
||||
await sendEmail({
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('Error sending email', e);
|
||||
return false;
|
||||
// This is a raw email
|
||||
const result = await provider.sendRawEmail(params);
|
||||
return result.success;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get rendered email for given template, context, and locale
|
||||
* Get rendered email for given template, context, and locale
|
||||
*/
|
||||
async function getTemplate<T extends Template>({
|
||||
export async function getTemplate<T extends Template>({
|
||||
template,
|
||||
context,
|
||||
locale,
|
||||
locale = routing.defaultLocale,
|
||||
}: {
|
||||
template: T;
|
||||
context: Omit<
|
||||
Parameters<(typeof EmailTemplates)[T]>[0],
|
||||
'locale' | 'messages'
|
||||
>;
|
||||
locale: Locale;
|
||||
context: Record<string, any>;
|
||||
locale?: Locale;
|
||||
}) {
|
||||
const mainTemplate = EmailTemplates[template];
|
||||
const messages = await getMessagesForLocale(locale);
|
||||
@ -94,7 +49,7 @@ async function getTemplate<T extends Template>({
|
||||
messages,
|
||||
});
|
||||
|
||||
// get the subject from the messages
|
||||
// Get the subject from the messages
|
||||
const subject =
|
||||
'subject' in messages.Mail[template as keyof Messages['Mail']]
|
||||
? messages.Mail[template].subject
|
||||
@ -102,5 +57,6 @@ async function getTemplate<T extends Template>({
|
||||
|
||||
const html = await render(email);
|
||||
const text = await render(email, { plainText: true });
|
||||
|
||||
return { html, text, subject };
|
||||
}
|
||||
|
@ -1,36 +1,115 @@
|
||||
import { websiteConfig } from '@/config';
|
||||
import { SendEmailHandler } from '@/mail/types';
|
||||
import { MailProvider, SendEmailResult, SendRawEmailParams, SendTemplateParams } from '@/mail/types';
|
||||
import { getTemplate } from '@/mail/mail';
|
||||
import { Resend } from 'resend';
|
||||
|
||||
const apiKey = process.env.RESEND_API_KEY || 'test_api_key';
|
||||
|
||||
const resend = new Resend(apiKey);
|
||||
|
||||
/**
|
||||
* https://resend.com/docs/send-with-nextjs
|
||||
* Resend mail provider implementation
|
||||
*/
|
||||
export const sendEmail: SendEmailHandler = async ({ to, subject, html }) => {
|
||||
if (!process.env.RESEND_API_KEY) {
|
||||
console.warn('RESEND_API_KEY not set, skipping email send');
|
||||
return false;
|
||||
export class ResendProvider implements MailProvider {
|
||||
private resend: Resend;
|
||||
private from: string;
|
||||
|
||||
/**
|
||||
* Initialize Resend provider with API key
|
||||
*/
|
||||
constructor() {
|
||||
if (!process.env.RESEND_API_KEY) {
|
||||
throw new Error('RESEND_API_KEY environment variable is not set.');
|
||||
}
|
||||
|
||||
if (!websiteConfig.mail.from) {
|
||||
throw new Error('Default from email address is not set in websiteConfig.');
|
||||
}
|
||||
|
||||
const apiKey = process.env.RESEND_API_KEY;
|
||||
this.resend = new Resend(apiKey);
|
||||
this.from = websiteConfig.mail.from;
|
||||
}
|
||||
|
||||
if (!websiteConfig.mail.from || !to || !subject || !html) {
|
||||
console.warn('Missing required fields for email send', { from: websiteConfig.mail.from, to, subject, html });
|
||||
return false;
|
||||
/**
|
||||
* Send an email using a template
|
||||
* @param params Parameters for sending a templated email
|
||||
* @returns Send result
|
||||
*/
|
||||
public async sendTemplate(params: SendTemplateParams): Promise<SendEmailResult> {
|
||||
const { to, template, context, locale } = params;
|
||||
|
||||
try {
|
||||
// Get rendered template
|
||||
const mailTemplate = await getTemplate({
|
||||
template,
|
||||
context,
|
||||
locale,
|
||||
});
|
||||
|
||||
// Send using raw email
|
||||
return this.sendRawEmail({
|
||||
to,
|
||||
subject: mailTemplate.subject,
|
||||
html: mailTemplate.html,
|
||||
text: mailTemplate.text,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error sending template email:', error);
|
||||
return {
|
||||
success: false,
|
||||
error,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const { data, error } = await resend.emails.send({
|
||||
from: websiteConfig.mail.from,
|
||||
to,
|
||||
subject,
|
||||
html,
|
||||
});
|
||||
/**
|
||||
* Send a raw email
|
||||
* @param params Parameters for sending a raw email
|
||||
* @returns Send result
|
||||
*/
|
||||
public async sendRawEmail(params: SendRawEmailParams): Promise<SendEmailResult> {
|
||||
const { to, subject, html, text } = params;
|
||||
|
||||
if (error) {
|
||||
console.error('Error sending email', error);
|
||||
return false;
|
||||
if (!this.from || !to || !subject || !html) {
|
||||
console.warn('Missing required fields for email send', { from: this.from, to, subject, html });
|
||||
return {
|
||||
success: false,
|
||||
error: 'Missing required fields',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await this.resend.emails.send({
|
||||
from: this.from,
|
||||
to,
|
||||
subject,
|
||||
html,
|
||||
text,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Error sending email', error);
|
||||
return {
|
||||
success: false,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
messageId: data?.id,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error sending email:', error);
|
||||
return {
|
||||
success: false,
|
||||
error,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* Get the provider name
|
||||
* @returns Provider name
|
||||
*/
|
||||
public getProviderName(): string {
|
||||
return 'resend';
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,84 @@
|
||||
import { Locale, Messages } from 'next-intl';
|
||||
import { EmailTemplates } from './emails';
|
||||
|
||||
/**
|
||||
* Base email component props
|
||||
*/
|
||||
export interface BaseEmailProps {
|
||||
locale: Locale;
|
||||
messages: Messages;
|
||||
}
|
||||
|
||||
export interface SendEmailProps {
|
||||
/**
|
||||
* Common email sending parameters
|
||||
*/
|
||||
export interface SendEmailParams {
|
||||
to: string;
|
||||
subject: string;
|
||||
text: string;
|
||||
html?: string;
|
||||
text?: string;
|
||||
html: string;
|
||||
from?: string;
|
||||
}
|
||||
|
||||
export type Template = keyof typeof EmailTemplates;
|
||||
|
||||
export type SendEmailHandler = (params: SendEmailProps) => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Email provider, currently only Resend is supported
|
||||
* Result of sending an email
|
||||
*/
|
||||
export interface EmailProvider {
|
||||
send: SendEmailHandler;
|
||||
export interface SendEmailResult {
|
||||
success: boolean;
|
||||
messageId?: string;
|
||||
error?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Email template types
|
||||
*/
|
||||
export type Template = keyof typeof EmailTemplates;
|
||||
|
||||
/**
|
||||
* Parameters for sending an email using a template
|
||||
*/
|
||||
export interface SendTemplateParams {
|
||||
to: string;
|
||||
template: Template;
|
||||
context: Record<string, any>;
|
||||
locale?: Locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for sending a raw email
|
||||
*/
|
||||
export interface SendRawEmailParams {
|
||||
to: string;
|
||||
subject: string;
|
||||
html: string;
|
||||
text?: string;
|
||||
locale?: Locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mail provider configuration
|
||||
*/
|
||||
export interface MailConfig {
|
||||
defaultFromEmail: string;
|
||||
defaultLocale: Locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mail provider interface
|
||||
*/
|
||||
export interface MailProvider {
|
||||
/**
|
||||
* Send an email using a template
|
||||
*/
|
||||
sendTemplate(params: SendTemplateParams): Promise<SendEmailResult>;
|
||||
|
||||
/**
|
||||
* Send a raw email
|
||||
*/
|
||||
sendRawEmail(params: SendRawEmailParams): Promise<SendEmailResult>;
|
||||
|
||||
/**
|
||||
* Get the provider's name
|
||||
*/
|
||||
getProviderName(): string;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user