Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/backend/src/app/api/latest/emails/send-email/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ import { getEmailConfig, sendEmail } from "@/lib/emails";
import { getNotificationCategoryByName, hasNotificationEnabled } from "@/lib/notification-categories";
import { getPrismaClientForTenancy } from "@/prisma-client";
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
import { KnownErrors } from "@stackframe/stack-shared";
import { adaptSchema, serverOrHigherAuthTypeSchema, templateThemeIdSchema, yupArray, yupMixed, yupNumber, yupObject, yupRecord, yupString } from "@stackframe/stack-shared/dist/schema-fields";
import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
import { throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { StatusError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { unsubscribeLinkVerificationCodeHandler } from "../unsubscribe-link/verification-handler";
import { KnownErrors } from "@stackframe/stack-shared";

type UserResult = {
user_id: string,
Expand All @@ -19,6 +18,7 @@ export const POST = createSmartRouteHandler({
metadata: {
summary: "Send email",
description: "Send an email to a list of users. The content field should contain either {html, subject, notification_category_name} for HTML emails or {template_id, variables} for template-based emails.",
tags: ["Emails"],
},
request: yupObject({
auth: yupObject({
Expand Down
6 changes: 6 additions & 0 deletions docs/docs-platform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ pages:

- path: concepts/backend-integration.mdx
platforms: ["next", "react", "js", "python"]

- path: concepts/emails.mdx
platforms: ["next", "react", "js"] # No Python (server-side email functionality)

# Components (React-like only)
- path: components/overview.mdx
Expand Down Expand Up @@ -227,6 +230,9 @@ pages:

- path: sdk/types/user.mdx
platforms: ["next", "react", "js"] # No Python

- path: sdk/types/email.mdx
platforms: ["next", "react", "js"] # No Python

# SDK Hooks (React-like only)
- path: sdk/hooks/use-stack-app.mdx
Expand Down
1 change: 1 addition & 0 deletions docs/scripts/generate-functional-api-docs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const FUNCTIONAL_TAGS = [
'API Keys',
'CLI Authentication',
'Contact Channels',
'Emails',
'Oauth', // Note: OpenAPI uses "Oauth" not "OAuth"
'OTP',
'Password',
Expand Down
3 changes: 3 additions & 0 deletions docs/src/components/ui/method-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,9 @@ export function CollapsibleTypesSection({
case 'project': {
return 'bg-lime-50 dark:bg-lime-950/50 text-lime-700 dark:text-lime-300';
}
case 'sendemailoptions': {
return 'bg-rose-50 dark:bg-rose-950/50 text-rose-700 dark:text-rose-300';
}
default: {
return 'bg-gray-50 dark:bg-gray-950/50 text-gray-700 dark:text-gray-300';
}
Expand Down
197 changes: 197 additions & 0 deletions docs/templates/concepts/emails.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
---
title: Emails
description: Send custom emails to your users with Stack Auth's email system.
---

Stack Auth provides emails that allows you to send custom emails to your users. The system supports both custom HTML emails and template-based emails with theming.

## Email Types:
There are two types of emails that you can send to your users:
- **Transactional Emails**: Transactional emails are those required for your user to use your application. These emails cannot be opted out of.
- **Marketing Emails**: Marketing emails always contain an unsubscribe link and may be more general marketing material related to your application/company.

<Info>
Never send marketing emails as transactional emails, as this can quickly lead to your domain being blacklisted by email spam filters.
</Info>



## Overview

The email system provides:

- **Email Sending**: Send custom emails to users via the `sendEmail` method on `StackServerApp`
- **Email Templates**: Use predefined email templates for common authentication flows
- **Email Themes**: Apply consistent styling to your emails
- **Notification Categories**: Allow users to control which emails they receive

## Server-Side Email Sending

### Basic Email Sending

Use the `sendEmail` method on your server app to send emails to users:

```typescript
import { stackServerApp } from './stack';

// Send a custom HTML email
const result = await stackServerApp.sendEmail({
userIds: ['user-id-1', 'user-id-2'],
subject: 'Welcome to our platform!',
html: '<h1>Welcome!</h1><p>Thanks for joining us.</p>',
});

if (result.status === 'error') {
console.error('Failed to send email:', result.error);
}
```

### Template-Based Emails

Send emails using predefined templates with variables:

```typescript
// Send email using a template
const result = await stackServerApp.sendEmail({
userIds: ['user-id'],
templateId: 'welcome-template',
subject: 'Welcome to our platform!',
variables: {
userName: 'John Doe',
activationUrl: 'https://yourapp.com/activate/token123',
supportEmail: 'support@yourapp.com',
},
});
```

### Email Options

The `sendEmail` method accepts the following options:

```typescript
type SendEmailOptions = {
userIds: string[]; // Array of user IDs to send to
themeId?: string | null | false; // Theme to apply (optional)
subject?: string; // Email subject
notificationCategoryName?: string; // Notification category for user preferences
html?: string; // Custom HTML content
templateId?: string; // Template ID to use
variables?: Record<string, any>; // Template variables
};
```

### Error Handling

The `sendEmail` method returns a `Result` type that can contain specific errors:

```typescript
const result = await stackServerApp.sendEmail({
userIds: ['user-id'],
html: '<p>Hello!</p>',
subject: 'Test Email',
});

if (result.status === 'error') {
switch (result.error.code) {
case 'REQUIRES_CUSTOM_EMAIL_SERVER':
console.error('Please configure a custom email server');
break;
case 'SCHEMA_ERROR':
console.error('Invalid email data provided');
break;
case 'USER_ID_DOES_NOT_EXIST':
console.error('One or more user IDs do not exist');
break;
}
}
```



## Built-in Email Templates

Stack Auth provides several built-in email templates for common authentication flows:

- **Email Verification**: `email_verification` - Sent when users need to verify their email
- **Password Reset**: `password_reset` - Sent when users request password reset
- **Magic Link**: `magic_link` - Sent for passwordless authentication
- **Team Invitation**: `team_invitation` - Sent when users are invited to teams
- **Sign-in Invitation**: `sign_in_invitation` - Sent to invite users to sign up

These templates can be customized through the admin interface or programmatically.

## Email Configuration

Email configuration is managed through the Stack Auth dashboard or admin API, not directly in your application code. You have two options:

### Shared Email Provider (Development)

For development and testing, you can use Stack's shared email provider. This sends emails from `noreply@stackframe.co` and is configured through your project settings in the Stack Auth dashboard.

- Go to your project's Email settings in the dashboard
- Select "Shared" as your email server type
- No additional configuration required

### Custom Email Server (Production)

For production, configure your own SMTP server through the dashboard:

- Go to your project's Email settings in the dashboard
- Select "Custom SMTP server" as your email server type
- Configure the following settings:
- **Host**: Your SMTP server hostname (e.g., `smtp.yourprovider.com`)
- **Port**: SMTP port (typically 587 for TLS or 465 for SSL)
- **Username**: Your SMTP username
- **Password**: Your SMTP password
- **Sender Email**: The email address emails will be sent from
- **Sender Name**: The display name for your emails

The dashboard will automatically test your configuration when you save it.

## Notification Categories

Control which emails users receive by organizing them into notification categories:

```typescript
await stackServerApp.sendEmail({
userIds: ['user-id'],
html: '<p>New feature available!</p>',
subject: 'Product Updates',
notificationCategoryName: 'product_updates',
});
```

Users can then opt in or out of specific notification categories through their account settings.

## Best Practices

1. **Use Templates**: Leverage built-in templates for consistent branding and easier maintenance
2. **Handle Errors**: Always check the result status and handle potential errors
3. **Respect User Preferences**: Use notification categories to let users control what emails they receive
4. **Server-Side Only**: Always send emails from your server-side code, never from the client

## React Components Integration

Emails integrates seamlessly with Stack Auth's React components. Email verification, password reset, and other authentication emails are automatically sent when users interact with the provided components.

For custom email flows, use the `sendEmail` method from your server-side code:

```typescript
// In your API route or server action
import { stackServerApp } from '@stackframe/stack';

export async function inviteUser(email: string) {
const result = await stackServerApp.sendEmail({
userIds: [userId], // Get user ID first
templateId: 'invitation-template',
subject: 'You\'re invited!',
variables: {
inviteUrl: 'https://yourapp.com/invite/token123',
},
});

return result;
}
```

This email system gives you control over your application's email communications while maintaining the security and reliability of Stack Auth's infrastructure.
1 change: 1 addition & 0 deletions docs/templates/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"concepts/api-keys",
"concepts/backend-integration",
"concepts/custom-user-data",
"concepts/emails",
"concepts/oauth",
"concepts/auth-providers",
"concepts/orgs-and-teams",
Expand Down
6 changes: 6 additions & 0 deletions docs/templates/sdk/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ export const sdkSections = [
{ name: "ServerTeamProfile", href: "types/team-profile#serverteamprofile", icon: "type" },
]
},
{
title: "Email",
items: [
{ name: "SendEmailOptions", href: "types/email#sendemailoptions", icon: "type" },
]
},
{
title: "Hooks",
items: [
Expand Down
1 change: 1 addition & 0 deletions docs/templates/sdk/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"types/team-permission",
"types/team-profile",
"types/contact-channel",
"types/email",
"types/api-key",
"types/project",
"types/connected-account",
Expand Down
72 changes: 72 additions & 0 deletions docs/templates/sdk/objects/stack-app.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ exposing [`SECRET_SERVER_KEY`](../../rest-api/overview.mdx) on the client.
// NEXT_LINE_PLATFORM react-like
⤷ useUsers([options]): ServerUser[]; //$stack-link-to:#stackserverappuseusersoptions
createUser([options]): Promise<ServerUser>; //$stack-link-to:#stackserverappcreateuseroptions
sendEmail(options): Promise<Result<void, KnownErrors>>; //$stack-link-to:#stackserverappsendemailoptions

getTeam(id): Promise<ServerTeam | null>; //$stack-link-to:#stackserverappgetteamid
// NEXT_LINE_PLATFORM react-like
Expand Down Expand Up @@ -799,6 +800,77 @@ const user = await stackServerApp.createUser({
</MethodLayout>
</CollapsibleMethodSection>

<CollapsibleMethodSection method="sendEmail" signature="[options]" appType="StackServerApp" defaultOpen={false}>

<MethodLayout>
<MethodContent>

Send custom emails to users. You can send either custom HTML emails or use predefined templates with variables.

**Parameters:**
- `options` ([SendEmailOptions](../types/email#sendemailoptions)) - Email configuration and content

**Returns:** `Promise<Result<void, KnownErrors>>`

The method returns a `Result` object that can contain specific error types:

- `RequiresCustomEmailServer` - No custom email server configured
- `SchemaError` - Invalid email data provided
- `UserIdDoesNotExist` - One or more user IDs don't exist

</MethodContent>
<MethodAside>

<AsideSection title="Signature">

```typescript
declare function sendEmail(options: SendEmailOptions): Promise<Result<void, KnownErrors>>;
```
</AsideSection>
<AsideSection title="Examples">

<Tabs defaultValue="html-email">
<TabsList>
<TabsTrigger value="html-email">Send HTML Email</TabsTrigger>
<TabsTrigger value="template-email">Send Template Email</TabsTrigger>
</TabsList>
<TabsContent value="html-email">
```typescript
const result = await stackServerApp.sendEmail({
userIds: ['user-1', 'user-2'],
subject: 'Welcome to our platform!',
html: '<h1>Welcome!</h1><p>Thanks for joining us.</p>',
});

if (result.status === 'error') {
console.error('Failed to send email:', result.error);
}
```
</TabsContent>
<TabsContent value="template-email">
```typescript
const result = await stackServerApp.sendEmail({
userIds: ['user-1'],
templateId: 'welcome-template',
variables: {
userName: 'John Doe',
activationUrl: 'https://app.com/activate/token123',
},
});

if (result.status === 'error') {
console.error('Failed to send email:', result.error);
}
```
</TabsContent>
</Tabs>

</AsideSection>

</MethodAside>
</MethodLayout>
</CollapsibleMethodSection>

## Team Management

<CollapsibleMethodSection method="getTeam" signature="[id]" appType="StackServerApp" defaultOpen={false}>
Expand Down
Loading
Loading