Next.js Supabase V3 (#463)
Version 3 of the kit: - Radix UI replaced with Base UI (using the Shadcn UI patterns) - next-intl replaces react-i18next - enhanceAction deprecated; usage moved to next-safe-action - main layout now wrapped with [locale] path segment - Teams only mode - Layout updates - Zod v4 - Next.js 16.2 - Typescript 6 - All other dependencies updated - Removed deprecated Edge CSRF - Dynamic Github Action runner
This commit is contained in:
committed by
GitHub
parent
4912e402a3
commit
7ebff31475
@@ -1,66 +1,19 @@
|
||||
# Email Service Instructions
|
||||
# @kit/mailers — Email Service
|
||||
|
||||
This file contains guidance for working with the email service supporting Resend and Nodemailer.
|
||||
## Non-Negotiables
|
||||
|
||||
## Basic Usage
|
||||
1. ALWAYS use `getMailer()` factory from `@kit/mailers` — never instantiate mailer directly
|
||||
2. ALWAYS use `@kit/email-templates` renderers for HTML — never write inline HTML
|
||||
3. ALWAYS render template first (`renderXxxEmail()`), then pass `{ html, subject }` to `sendEmail()`
|
||||
4. NEVER hardcode sender/recipient addresses — use environment config
|
||||
|
||||
```typescript
|
||||
import { getMailer } from '@kit/mailers';
|
||||
import { renderAccountDeleteEmail } from '@kit/email-templates';
|
||||
## Workflow
|
||||
|
||||
async function sendSimpleEmail() {
|
||||
// Get mailer instance
|
||||
const mailer = await getMailer();
|
||||
1. Render: `const { html, subject } = await renderXxxEmail(props)`
|
||||
2. Get mailer: `const mailer = await getMailer()`
|
||||
3. Send: `await mailer.sendEmail({ to, from, subject, html })`
|
||||
|
||||
// Send simple email
|
||||
await mailer.sendEmail({
|
||||
to: 'user@example.com',
|
||||
from: 'noreply@yourdomain.com',
|
||||
subject: 'Welcome!',
|
||||
html: '<h1>Welcome!</h1><p>Thank you for joining us.</p>',
|
||||
});
|
||||
}
|
||||
## Exemplars
|
||||
|
||||
async function sendComplexEmail() {
|
||||
// Send with email template
|
||||
const { html, subject } = await renderAccountDeleteEmail({
|
||||
userDisplayName: user.name,
|
||||
productName: 'My SaaS App',
|
||||
});
|
||||
|
||||
await mailer.sendEmail({
|
||||
to: user.email,
|
||||
from: 'noreply@yourdomain.com',
|
||||
subject,
|
||||
html,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Email Templates
|
||||
|
||||
Email templates are located in `@kit/email-templates` and return `{ html, subject }`:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
renderAccountDeleteEmail,
|
||||
renderWelcomeEmail,
|
||||
renderPasswordResetEmail
|
||||
} from '@kit/email-templates';
|
||||
|
||||
// Render template
|
||||
const { html, subject } = await renderWelcomeEmail({
|
||||
userDisplayName: 'John Doe',
|
||||
loginUrl: 'https://app.com/login'
|
||||
});
|
||||
|
||||
// Send rendered email
|
||||
const mailer = await getMailer();
|
||||
|
||||
await mailer.sendEmail({
|
||||
to: user.email,
|
||||
from: 'welcome@yourdomain.com',
|
||||
subject,
|
||||
html,
|
||||
});
|
||||
```
|
||||
- Contact form: `apps/web/app/[locale]/(marketing)/contact/_lib/server/server-actions.ts`
|
||||
- Invitation dispatch: `packages/features/team-accounts/src/server/services/account-invitations-dispatcher.service.ts`
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import eslintConfigBase from '@kit/eslint-config/base.js';
|
||||
|
||||
export default eslintConfigBase;
|
||||
@@ -1,33 +1,27 @@
|
||||
{
|
||||
"name": "@kit/mailers",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"format": "prettier --check \"**/*.{ts,tsx}\"",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/mailers-shared": "workspace:*",
|
||||
"@kit/nodemailer": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/resend": "workspace:*",
|
||||
"@kit/shared": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@types/node": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"private": true,
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/mailers-shared": "workspace:*",
|
||||
"@kit/nodemailer": "workspace:*",
|
||||
"@kit/resend": "workspace:*",
|
||||
"@kit/shared": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"zod": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from 'zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
const MAILER_PROVIDERS = [
|
||||
'nodemailer',
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import eslintConfigBase from '@kit/eslint-config/base.js';
|
||||
|
||||
export default eslintConfigBase;
|
||||
@@ -1,33 +1,28 @@
|
||||
{
|
||||
"name": "@kit/nodemailer",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"format": "prettier --check \"**/*.{ts,tsx}\"",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"nodemailer": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/mailers-shared": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@types/nodemailer": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"private": true,
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"nodemailer": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/mailers-shared": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@types/nodemailer": "catalog:",
|
||||
"zod": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import 'server-only';
|
||||
|
||||
import { z } from 'zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { Mailer, MailerSchema } from '@kit/mailers-shared';
|
||||
|
||||
import { getSMTPConfiguration } from './smtp-configuration';
|
||||
|
||||
type Config = z.infer<typeof MailerSchema>;
|
||||
type Config = z.output<typeof MailerSchema>;
|
||||
|
||||
export function createNodemailerService() {
|
||||
return new Nodemailer();
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import eslintConfigBase from '@kit/eslint-config/base.js';
|
||||
|
||||
export default eslintConfigBase;
|
||||
@@ -1,30 +1,24 @@
|
||||
{
|
||||
"name": "@kit/resend",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"format": "prettier --check \"**/*.{ts,tsx}\"",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/mailers-shared": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@types/node": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"private": true,
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/mailers-shared": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"zod": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import 'server-only';
|
||||
|
||||
import { z } from 'zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { Mailer, MailerSchema } from '@kit/mailers-shared';
|
||||
|
||||
type Config = z.infer<typeof MailerSchema>;
|
||||
type Config = z.output<typeof MailerSchema>;
|
||||
|
||||
const RESEND_API_KEY = z
|
||||
.string({
|
||||
description: 'The API key for the Resend API',
|
||||
required_error: 'Please provide the API key for the Resend API',
|
||||
error: 'Please provide the API key for the Resend API',
|
||||
})
|
||||
.parse(process.env.RESEND_API_KEY);
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import eslintConfigBase from '@kit/eslint-config/base.js';
|
||||
|
||||
export default eslintConfigBase;
|
||||
@@ -1,28 +1,23 @@
|
||||
{
|
||||
"name": "@kit/mailers-shared",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"format": "prettier --check \"**/*.{ts,tsx}\"",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/eslint-config": "workspace:*",
|
||||
"@kit/prettier-config": "workspace:*",
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"private": true,
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"zod": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { MailerSchema } from './schema/mailer.schema';
|
||||
|
||||
export abstract class Mailer<Res = unknown> {
|
||||
abstract sendEmail(data: z.infer<typeof MailerSchema>): Promise<Res>;
|
||||
abstract sendEmail(data: z.output<typeof MailerSchema>): Promise<Res>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { z } from 'zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
export const MailerSchema = z
|
||||
.object({
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
import 'server-only';
|
||||
|
||||
import { z } from 'zod';
|
||||
import * as z from 'zod';
|
||||
|
||||
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.',
|
||||
required_error: `Please provide the variable EMAIL_USER`,
|
||||
error: `Please provide the variable EMAIL_USER`,
|
||||
}),
|
||||
pass: z.string({
|
||||
description: 'This is the password for the email account',
|
||||
required_error: `Please provide the variable EMAIL_PASSWORD`,
|
||||
error: `Please provide the variable EMAIL_PASSWORD`,
|
||||
}),
|
||||
host: z.string({
|
||||
description: 'This is the SMTP host for the email provider',
|
||||
required_error: `Please provide the variable EMAIL_HOST`,
|
||||
error: `Please provide the variable EMAIL_HOST`,
|
||||
}),
|
||||
port: z.number({
|
||||
description:
|
||||
'This is the port for the email provider. Normally 587 or 465.',
|
||||
required_error: `Please provide the variable EMAIL_PORT`,
|
||||
error: `Please provide the variable EMAIL_PORT`,
|
||||
}),
|
||||
secure: z.boolean({
|
||||
description: 'This is whether the connection is secure or not',
|
||||
required_error: `Please provide the variable EMAIL_TLS`,
|
||||
error: `Please provide the variable EMAIL_TLS`,
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user