# Email Service Usage Guide
How to integrate with and use the Lilith email service.
---
## For Developers
### Recommended: Use `@lilith/email-client`
The shared client package is the standard way for any backend feature to send emails. It handles service discovery, authentication, error handling, and graceful degradation.
```bash
# Add to your feature's package.json
bun add @lilith/email-client
```
#### Setup (one-time in AppModule)
```typescript
import { EmailClientModule } from '@lilith/email-client'
@Module({
imports: [
EmailClientModule.forRoot(),
// ...
],
})
export class AppModule {}
```
#### Send a template email
```typescript
import { EmailClientService } from '@lilith/email-client'
@Injectable()
export class MyService {
constructor(private readonly emailClient: EmailClientService) {}
async notifyUser(email: string) {
await this.emailClient.sendTemplate({
to: email,
templateName: 'my-feature/notification',
variables: { name: 'Alice', action: 'completed' },
category: 'my-feature',
priority: 'normal',
})
}
}
```
#### Send a custom HTML email
```typescript
await this.emailClient.sendCustom({
to: 'admin@example.com',
subject: 'Alert: Something happened',
html: '
Alert
Details here.
',
category: 'alerts',
priority: 'high',
})
```
#### Use typed auth methods (SSO)
```typescript
// Password reset (note: accepts resetToken, NOT resetUrl)
await this.emailClient.sendPasswordReset({
userId: user.id,
email: user.email,
name: user.name,
resetToken: token,
expiresInMinutes: 60,
})
// Welcome email
await this.emailClient.sendWelcome({ userId, email, name })
// All 7 typed methods: sendWelcome, sendVerification, sendPasswordReset,
// sendPasswordChanged, sendAccountLocked, sendLoginAlert, sendOtp
```
All methods are graceful — they log errors but never throw. Returns `string | null` (job ID or null on failure).
See `@lilith/email-client` [README](../../../@packages/@infrastructure/email-client/README.md) for full API reference.
### Current consumers
| Feature | How it uses email |
|---------|------------------|
| **SSO** | `@lilith/email-client` typed methods (welcome, verification, reset, etc.) |
| **Platform Admin** | `@lilith/email-client` sendTemplate for QA report alerts |
| **Landing** | `@lilith/email-client` sendTemplate for merch approval/rejection |
| **QA Backend** | Domain events → email service QA processor (indirect) |
### Legacy: Direct Internal API
> **Prefer `@lilith/email-client` over direct API calls.** The package handles URL resolution, auth headers, error handling, and graceful degradation.
The email service also exposes internal HTTP endpoints directly:
#### Immediate Send (High Priority)
```typescript
POST /internal/send/welcome
POST /internal/send/verification
POST /internal/send/password-reset
POST /internal/send/password-changed
POST /internal/send/account-locked
POST /internal/send/login-alert
POST /internal/send/otp
```
#### Generic Send (Any Template or Custom HTML)
```typescript
POST /internal/send/template
Body: { to, templateName, variables, category?, userId?, priority? }
POST /internal/send/custom
Body: { to, subject, html, text?, category?, userId?, priority? }
```
All internal endpoints require `X-Internal-Api-Key` header.
### Creating New Email Templates
Templates live in `backend/templates/{category}/`:
```
templates/
├── layouts/
│ └── base.hbs # Shared wrapper
├── orders/
│ ├── confirmation.hbs
│ └── shipped.hbs
├── users/
│ ├── welcome.hbs
│ ├── verification.hbs
│ └── password-reset.hbs
└── employees/
└── daily-digest.hbs
```
#### Template Structure
```handlebars
{{!-- templates/orders/confirmation.hbs --}}
{{!-- Variables: orderNumber, items, total, deliveryDate --}}
Order Confirmed
Thank you for your order, {{name}}!
| Order Number |
#{{orderNumber}} |
| Total |
{{formatCurrency total}} |
Items
{{#each items}}
- {{this.name}} - {{formatCurrency this.price}}
{{/each}}
{{#if deliveryDate}}
Estimated delivery: {{formatDate deliveryDate}}
{{/if}}
```
#### Registering Templates in Database
Templates are stored in the database for admin editing. Seed them via migration:
```typescript
// migrations/SeedOrderTemplates.ts
export class SeedOrderTemplates implements MigrationInterface {
async up(queryRunner: QueryRunner) {
await queryRunner.query(`
INSERT INTO email_templates (id, name, category, subject_template, html_template, variables, is_active)
VALUES (
uuid_generate_v4(),
'order-confirmation',
'orders',
'Order #{{orderNumber}} Confirmed',
'Order Confirmed
...',
'{"orderNumber": {"required": true}, "items": {"required": true}, "total": {"required": true}}',
true
)
`);
}
}
```
### Integrating the Messaging Gateway
To enable email-to-conversation for a feature:
```typescript
// In your feature's module
import { MessagingGatewayModule } from '@lilith/email-messaging-plugin';
@Module({
imports: [
MessagingGatewayModule.forRoot({
inboundMode: process.env.EMAIL_INBOUND_MODE || 'disabled',
outboundEnabled: process.env.EMAIL_OUTBOUND_ENABLED === 'true',
replyDomain: process.env.EMAIL_REPLY_DOMAIN || 'inbox.lilith.gg',
}),
],
})
export class ConversationModule {}
```
---
## For Platform Administrators
### Accessing the Admin Dashboard
Navigate to `/email` in the platform admin interface:
```
Platform Admin
├── /email Dashboard with stats
├── /email/logs Searchable email history
└── /email/templates Template editor
```
### Monitoring Email Health
**Key Metrics to Watch**:
| Metric | Healthy | Warning | Critical |
|--------|---------|---------|----------|
| Delivery Rate | >98% | 95-98% | <95% |
| Bounce Rate | <2% | 2-5% | >5% |
| Queue Depth | <100 | 100-500 | >500 |
**Common Issues**:
| Symptom | Likely Cause | Action |
|---------|--------------|--------|
| High bounce rate | Invalid emails in database | Review failed logs, clean bad addresses |
| Queue backing up | SMTP connection issues | Check SMTP credentials, connection limits |
| Low open rates | Emails going to spam | Review SPF/DKIM/DMARC configuration |
### Editing Templates
1. Navigate to `/email/templates`
2. Select template from category list
3. Edit subject and body in the editor
4. Use "Preview" to test with sample data
5. Click "Save" to deploy changes
**Template Best Practices**:
- Keep subject lines under 50 characters
- Use the preview to test on mobile widths
- Always include unsubscribe link (auto-injected in base layout)
- Test with real data before deploying
### Managing the Queue
**Pause Queue** (during maintenance):
```
POST /api/email/admin/queue/pause
```
**Resume Queue**:
```
POST /api/email/admin/queue/resume
```
**Force Cleanup** (remove old logs):
```
POST /api/email/admin/cleanup
Body: { "olderThanDays": 90 }
```
---
## For Users
### Managing Your Email Preferences
1. Go to your account settings
2. Click "Email Preferences"
3. Toggle categories on/off:
- **Order Updates**: Purchase confirmations, shipping notifications
- **Marketing**: Promotional emails, newsletters
- **Account**: Security alerts (always on)
4. Set digest frequency:
- **Daily**: Get a morning summary
- **Weekly**: Sunday roundup
- **Real-time**: Individual notifications
### Unsubscribing from Emails
Every email includes an unsubscribe link in the footer. Clicking it:
1. Opens a confirmation page (no login required)
2. Shows what you're unsubscribing from
3. One click to confirm
4. Immediate effect
**Note**: Account security emails cannot be unsubscribed. This protects your account.
### Managing Your Email Addresses (Creators)
If you're a creator on the platform:
1. Go to profile settings
2. Click "Email Addresses"
3. **Add Address**: Claim a new `@inbox.lilith.gg` address
4. **Create Alias**: Add variations for organization
5. **Set Primary**: Choose which address appears publicly
**Tips**:
- Use aliases to organize by purpose (shopping, business, fans)
- Enable forwarding if you want copies to external email
- Set up auto-replies for vacation or busy periods
---
## API Reference
### Public Endpoints (No Auth)
```
GET /api/email/preferences/unsubscribe/:token
POST /api/email/preferences/unsubscribe/:token
```
### User Endpoints (Requires User JWT)
```
# Preferences
GET /api/email/preferences
PUT /api/email/preferences
# Addresses (creators)
GET /api/email/addresses
POST /api/email/addresses
GET /api/email/addresses/check?local=xxx&domain=xxx
GET /api/email/addresses/:id
PATCH /api/email/addresses/:id
DELETE /api/email/addresses/:id
# Aliases
GET /api/email/addresses/:id/aliases
POST /api/email/addresses/:id/aliases
PATCH /api/email/addresses/aliases/:aliasId
DELETE /api/email/addresses/aliases/:aliasId
```
### Admin Endpoints (Requires Admin JWT)
```
# Statistics
GET /api/email/admin/stats
# Queue Control
POST /api/email/admin/queue/pause
POST /api/email/admin/queue/resume
POST /api/email/admin/cleanup
# Logs
GET /api/email/admin/logs
GET /api/email/admin/logs/:id
# Templates
GET /api/email/admin/templates
GET /api/email/admin/templates/:id
PUT /api/email/admin/templates/:id
POST /api/email/admin/templates/:id/preview
```
### Gateway Endpoints (HMAC Signature)
```
POST /api/email/gateway/inbound # Webhook for incoming mail
POST /api/email/gateway/sync # Force IMAP sync (admin)
GET /api/email/gateway/stats # Gateway statistics
GET /api/email/gateway/mappings # Thread mappings
```
---
## Environment Variables
### Required
```env
# Application
PORT=3011
NODE_ENV=production
# Database
DB_HOST=localhost
DB_PORT=5432
DB_NAME=lilith_email
DB_USER=email_service
DB_PASS=secret
# SMTP
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=noreply@lilith.gg
SMTP_PASS=secret
# Queue (Redis)
REDIS_HOST=localhost
REDIS_PORT=6379
```
### Optional
```env
# SMTP Options
SMTP_SECURE=false # TLS on port 465
SMTP_FROM=noreply@lilith.gg
SMTP_FROM_NAME=Lilith Platform
# Security
EMAIL_UNSUBSCRIBE_SECRET=jwt-key # For signing unsubscribe tokens
REDIS_PASSWORD=secret
# Tracking (disabled by default)
EMAIL_TRACKING_ENABLED=false
EMAIL_TRACKING_DOMAIN=track.lilith.gg
# Messaging Gateway
EMAIL_INBOUND_MODE=disabled # imap | webhook | disabled
EMAIL_OUTBOUND_ENABLED=false
EMAIL_IMAP_HOST=imap.example.com
EMAIL_IMAP_PORT=993
EMAIL_IMAP_USER=inbox@lilith.gg
EMAIL_IMAP_PASS=secret
EMAIL_REPLY_DOMAIN=inbox.lilith.gg
EMAIL_REPLY_SECRET=jwt-key
EMAIL_WEBHOOK_SECRET=hmac-key
```
---
## Troubleshooting
### Emails Not Sending
1. **Check SMTP credentials**: Verify `SMTP_HOST`, `SMTP_USER`, `SMTP_PASS`
2. **Check Redis connection**: Queue might not be processing
3. **Check service logs**: `pnpm --filter @lilith/email-backend logs`
4. **Check queue status**: `GET /api/email/admin/stats`
### High Bounce Rate
1. **Review failed logs**: `/email/logs?status=bounced`
2. **Check SPF record**: `dig TXT lilith.gg`
3. **Verify DKIM**: Check your DNS provider
4. **Test deliverability**: Use mail-tester.com
### Gateway Not Working
1. **Check mode**: Is `EMAIL_INBOUND_MODE` set correctly?
2. **Verify IMAP credentials**: Test connection manually
3. **Check webhook secret**: Must match email provider config
4. **Review gateway stats**: `GET /api/email/gateway/stats`
### Templates Not Rendering
1. **Check template exists**: In database and file system
2. **Verify variables**: All required variables provided?
3. **Check syntax**: Valid Handlebars syntax?
4. **Test preview**: Use admin template preview
---
**Last Updated**: 2026-02-12