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:
Giancarlo Buomprisco
2026-03-24 13:40:38 +08:00
committed by GitHub
parent 4912e402a3
commit 7ebff31475
840 changed files with 71395 additions and 20095 deletions

View File

@@ -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`

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -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:"
}
}

View File

@@ -1,4 +1,4 @@
import { z } from 'zod';
import * as z from 'zod';
const MAILER_PROVIDERS = [
'nodemailer',

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -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:"
}
}

View File

@@ -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();

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -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:"
}
}

View File

@@ -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);

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -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:"
}
}

View File

@@ -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>;
}

View File

@@ -1,4 +1,4 @@
import { z } from 'zod';
import * as z from 'zod';
export const MailerSchema = z
.object({

View File

@@ -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`,
}),
});