email configuration
email configuration
this guide covers all configuration options for the email system
table of contents
environment variables
required
# Resend API Key
# Get from: https://resend.com/api-keys
RESEND_API_KEY=re_xxxxxxxxxxxxxxxxxxxxxxxxxx
```typescript
### optional
Sender Configuration
RESEND_FROM_EMAIL=noreply@suitsbooks.com RESEND_FROM_NAME=SuitsBooks RESEND_REPLY_TO=support@suitsbooks.com
Base URL for links in emails
NEXT_PUBLIC_BASE_URL=https://app.suitsbooks.com
Development Mode
Set to "true" to log emails instead of sending
EMAIL_TEST_MODE=false
Node environment
NODE_ENV=production
### environment-specific settings
Development (.env.local)
EMAIL_TEST_MODE=true NEXT_PUBLIC_BASE_URL=http://localhost:3000
Staging (.env.staging)
EMAIL_TEST_MODE=false NEXT_PUBLIC_BASE_URL=https://staging.suitsbooks.com
Production (.env.production)
EMAIL_TEST_MODE=false NEXT_PUBLIC_BASE_URL=https://app.suitsbooks.com
---
## email config
located in `lib/email/config.ts`:
```typescript
export const EMAIL_CONFIG = {
// Default sender email (must be verified in Resend)
defaultFrom: process.env.RESEND_FROM_EMAIL || "noreply@suitsbooks.com",
// Default sender name
defaultFromName: process.env.RESEND_FROM_NAME || "SuitsBooks",
// Default reply-to address
defaultReplyTo: process.env.RESEND_REPLY_TO || "support@suitsbooks.com",
// Resend API key
apiKey: process.env.RESEND_API_KEY || "",
// Base URL for email links
baseUrl: process.env.NEXT_PUBLIC_BASE_URL || "https://app.suitsbooks.com",
// Is development environment
isDevelopment: process.env.NODE_ENV === "development",
// Test mode - logs emails instead of sending
testMode: process.env.EMAIL_TEST_MODE === "true",
// Environments where real emails are sent
allowedEnvironments: ["production", "staging"],
};
```typescript
### customizing the config
to override these settings modify your `.env` file or update `lib/email/config.ts` directly
---
## template config
each template has associated configuration in `lib/email/config.ts`:
```typescript
export const TEMPLATE_CONFIG: Record<EmailTemplateId, TemplateConfiguration> = {
invoice: {
// Default subject (supports variables)
defaultSubject: "Invoice from {{AUTHOR_NAME}}",
// Whether authentication is required
requiresAuth: true,
// Category for grouping
category: "transactional",
},
"password-reset": {
defaultSubject: "Reset your password",
requiresAuth: false, // No auth needed for password reset
category: "transactional",
},
welcome: {
defaultSubject: "Welcome to SuitsBooks!",
requiresAuth: false, // No auth needed for welcome email
category: "notification",
},
// ... more templates
};
```typescript
### template configuration options
| option | type | description |
|--------|------|-------------|
| `defaultsubject` | string | default subject line (supports variables) |
| `requiresauth` | boolean | whether auth headers are required |
| `category` | string | email category: "transactional" "marketing" "notification" |
### adding a new template config
```typescript
// In lib/email/config.ts
export const TEMPLATE_CONFIG = {
// ... existing templates
"my-new-template": {
defaultSubject: "My New Template Subject",
requiresAuth: true,
category: "notification",
},
};
```typescript
---
## rate limit config
default rate limits in `lib/email/config.ts`:
```typescript
export const DEFAULT_RATE_LIMITS = {
// Maximum emails per minute (per user)
maxEmailsPerMinute: 10,
// Maximum emails per hour (per user)
maxEmailsPerHour: 100,
// Maximum emails per day (per user)
maxEmailsPerDay: 500,
// Duplicate detection window (seconds)
duplicateWindowSeconds: 60,
// Threshold for bulk confirmation
bulkThreshold: 10,
};
```typescript
### customizing rate limits
#### per-hook customization
```typescript
const { sendEmail } = useSendEmail({
maxEmailsPerMinute: 5, // Stricter limit
maxEmailsPerHour: 50,
maxEmailsPerDay: 200,
});
```typescript
#### global customization
modify `default_rate_limits` in `lib/email/config.ts`:
```typescript
export const DEFAULT_RATE_LIMITS = {
maxEmailsPerMinute: 20, // Increased from 10
maxEmailsPerHour: 200, // Increased from 100
maxEmailsPerDay: 1000, // Increased from 500
// ...
};
```typescript
### rate limit behavior
1 **per user**: rate limits are tracked per user (stored in localstorage)
2 **rolling window**: counts are based on rolling time windows
3 **automatic cleanup**: old entries are cleaned up daily
4 **persistence**: state persists across page refreshes
---
## validation config
validation rules in `lib/email/config.ts`:
```typescript
export const VALIDATION = {
// RFC 5322 compliant email regex
emailRegex: /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
// Maximum subject length
maxSubjectLength: 200,
// Maximum HTML body size (bytes)
maxHtmlBodySize: 1024 * 1024, // 1MB
// Maximum number of attachments
maxAttachments: 10,
// Maximum attachment size (bytes)
maxAttachmentSize: 10 * 1024 * 1024, // 10MB
// Maximum recipients per email
maxRecipients: 50,
};
```typescript
### validation behavior
| validation | behavior |
|------------|----------|
| email format | rejects invalid email formats |
| subject length | truncates or rejects long subjects |
| html body size | rejects oversized html |
| attachment count | rejects if too many attachments |
| attachment size | rejects oversized attachments |
| recipient count | rejects if too many recipients |
---
## hook config
default hook configuration in `lib/email/config.ts`:
```typescript
export const DEFAULT_HOOK_CONFIG: EmailHookConfig = {
// Rate limiting
enableRateLimiting: true,
maxEmailsPerMinute: 10,
maxEmailsPerHour: 100,
maxEmailsPerDay: 500,
// Duplicate detection
enableDuplicateDetection: true,
duplicateWindowSeconds: 60,
// Bulk sending
requireBulkConfirmation: true,
bulkThreshold: 10,
// Domain restrictions
allowedRecipientDomains: [], // Empty = all allowed
blockedRecipientDomains: [
"mailinator.com",
"guerrillamail.com",
"tempmail.com",
"throwaway.email",
"fakeinbox.com",
"10minutemail.com",
"temp-mail.org",
"getnada.com",
],
// Validation
enableEmailValidation: true,
// Storage
storeInDatabase: true,
};
```typescript
### hook config options
| option | type | default | description |
|--------|------|---------|-------------|
| `enableratelimiting` | boolean | true | enable rate limiting |
| `maxemailsperminute` | number | 10 | max emails per minute |
| `maxemailsperhour` | number | 100 | max emails per hour |
| `maxemailsperday` | number | 500 | max emails per day |
| `enableduplicatedetection` | boolean | true | detect duplicate emails |
| `duplicatewindowseconds` | number | 60 | duplicate detection window |
| `requirebulkconfirmation` | boolean | true | require confirmation for bulk |
| `bulkthreshold` | number | 10 | threshold for bulk confirmation |
| `allowedrecipientdomains` | string[] | [] | allowed domains (empty = all) |
| `blockedrecipientdomains` | string[] | [] | blocked domains |
| `enableemailvalidation` | boolean | true | validate email formats |
| `storeindatabase` | boolean | true | store emails in database |
| `onsuccess` | function | - | success callback |
| `onerror` | function | - | error callback |
| `onratelimited` | function | - | rate limited callback |
### configuration presets
#### high volume
```typescript
const { sendEmail } = useSendEmail({
maxEmailsPerMinute: 50,
maxEmailsPerHour: 500,
maxEmailsPerDay: 5000,
enableDuplicateDetection: false,
requireBulkConfirmation: false,
});
```typescript
#### strict security
```typescript
const { sendEmail } = useSendEmail({
maxEmailsPerMinute: 3,
maxEmailsPerHour: 20,
maxEmailsPerDay: 100,
enableDuplicateDetection: true,
duplicateWindowSeconds: 300, // 5 minutes
allowedRecipientDomains: ["company.com", "partner.com"],
requireBulkConfirmation: true,
bulkThreshold: 5,
});
```typescript
#### internal only
```typescript
const { sendEmail } = useSendEmail({
allowedRecipientDomains: [
"suitsbooks.com",
"suitsfinance.com",
],
blockedRecipientDomains: [], // Clear default blocked
});
```typescript
#### testing/development
```typescript
const { sendEmail } = useSendEmail({
enableRateLimiting: false,
enableDuplicateDetection: false,
storeInDatabase: false,
onSuccess: (response) => console.log("Sent:", response),
onError: (error) => console.error("Error:", error),
});
```typescript
---
## error messages
customizable error messages in `lib/email/config.ts`:
```typescript
export const ERROR_MESSAGES = {
RATE_LIMIT_MINUTE: "Too many emails sent. Please wait a minute before sending more.",
RATE_LIMIT_HOUR: "Hourly email limit reached. Please try again later.",
RATE_LIMIT_DAY: "Daily email limit reached. Please try again tomorrow.",
DUPLICATE_EMAIL: "This email was recently sent. Please wait before sending again.",
INVALID_EMAIL: "Invalid email address provided.",
BLOCKED_DOMAIN: "Emails to this domain are not allowed.",
MISSING_TEMPLATE: "Email template not found.",
MISSING_SUBJECT: "Email subject is required.",
MISSING_RECIPIENT: "Email recipient is required.",
MISSING_API_KEY: "Email service not configured.",
SEND_FAILED: "Failed to send email. Please try again.",
BULK_CONFIRMATION_REQUIRED: "Bulk email send requires confirmation.",
ATTACHMENT_TOO_LARGE: "Attachment exceeds maximum size limit.",
TOO_MANY_ATTACHMENTS: "Too many attachments. Maximum is 10.",
TOO_MANY_RECIPIENTS: "Too many recipients. Maximum is 50.",
UNAUTHORIZED: "You are not authorized to send emails.",
};
```typescript
### customizing error messages
modify `error_messages` in `lib/email/config.ts`:
```typescript
export const ERROR_MESSAGES = {
// ... existing messages
RATE_LIMIT_MINUTE: "Slow down! You can only send 10 emails per minute.",
// Custom message
};
```typescript
---
## domain configuration
### adding blocked domains
```typescript
// In lib/email/config.ts
export const DEFAULT_HOOK_CONFIG: EmailHookConfig = {
blockedRecipientDomains: [
// Existing
"mailinator.com",
"guerrillamail.com",
// Add more
"yopmail.com",
"trashmail.com",
"fakeemail.com",
],
};
```typescript
### using domain allowlist
```typescript
const { sendEmail } = useSendEmail({
// Only allow these domains
allowedRecipientDomains: [
"suitsbooks.com",
"gmail.com",
"outlook.com",
],
// Clear blocked list when using allowlist
blockedRecipientDomains: [],
});
```typescript
---
## resend configuration
### verify your domain
1 go to [resend domains](https://resend.com/domains)
2 add your domain (eg `suitsbooks.com`)
3 add dns records (spf dkim dmarc)
4 wait for verification
### api key scopes
when creating your api key consider:
- **full access**: can send and manage emails
- **sending only**: can only send emails (recommended for production)
### webhook configuration
resend can send webhooks for email events configure at [resend webhooks](https://resend.com/webhooks)
events available:
- `email.sent`
- `email.delivered`
- `email.delivery_delayed`
- `email.complained`
- `email.bounced`
- `email.opened`
- `email.clicked`
---
## best practices
### 1 use environment variables
never hardcode api keys or sensitive configuration:
```typescript
// Good
const apiKey = process.env.RESEND_API_KEY;
// Bad
const apiKey = "re_123456789";
```typescript
### 2 configure per environment
use different settings for different environments:
Development
EMAIL_TEST_MODE=true RESEND_FROM_EMAIL=dev@suitsbooks.com
Production
EMAIL_TEST_MODE=false RESEND_FROM_EMAIL=noreply@suitsbooks.com
### 3 set appropriate rate limits
consider your use case:
- **transactional**: lower limits stricter duplicate detection
- **marketing**: higher limits bulk confirmation
- **internal**: domain restrictions
### 4 monitor usage
track email metrics:
- sent count
- delivery rate
- open rate
- bounce rate
### 5 handle errors gracefully
always check for errors and provide user feedback:
```typescript
const result = await sendEmail({ ... });
if (!result.success) {
notification({
message: result.error,
success: false,
});
}
```typescript