System Emails Architecture
Philosophy: System Emails Don't Need Database Tracking
System emails (password resets, email verifications, security alerts) are platform infrastructure, not customer data. They should be:
- Independent - Not tied to any organization or domain in the database
- Reliable - Always work, even if database has issues
- Simple - No complex verification or tracking overhead
- Fast - Skip database lookups and validation
- Secure - Configuration from environment, not user-controlled database
Why System Emails Are Different
Customer Emails
- Belong to an organization
- Use customer's verified domain
- Tracked in database (Message, MessageEvent)
- Subject to rate limits and verification
- Can be paused/disabled per customer
- Need audit trail and analytics
System Emails
- Belong to the platform (Postchi)
- Use brand domain (
postchi.io) - NOT tracked in database ✅
- High priority, no rate limits
- Always enabled
- Logged to application logs only
Architecture
┌─────────────────────────────────────────────────────┐
│ Password Reset Request │
└────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ password-reset.service.ts │
│ ├─ Generate token (database tracked) │
│ ├─ Get config from ENV (not database) │
│ │ └─ SYSTEM_DKIM_PRIVATE_KEY │
│ └─ Queue to systemEmailQueue │
└────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ System Email Queue (Redis) │
│ ├─ Separate from customer queue │
│ ├─ Higher priority │
│ └─ More retry attempts │
└────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ system-email.worker.ts │
│ ├─ NO database lookups │
│ ├─ NO message tracking │
│ ├─ Direct SMTP send │
│ └─ Log to application logs │
└────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ SMTP Server (Direct Send) │
│ ├─ From: noreply@postchi.io │
│ ├─ DKIM signed with postchi.io keys │
│ └─ Dedicated brand IP (recommended) │
└─────────────────────────────────────────────────────┘
Configuration
Environment Variables (.env)
# System Email Configuration (for postchi.io brand emails)
SYSTEM_DKIM_SELECTOR=default
SYSTEM_DKIM_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
...your private key here...
-----END PRIVATE KEY-----"
DASHBOARD_URL=http://localhost:5173
No Database Configuration Required ✅
System emails bypass domain verification:
- ❌ No
Domainrecord needed forpostchi.io - ❌ No organization ownership
- ❌ No verification status checks
- ✅ Configuration from environment only
Benefits
1. Reliability
- Works even if database is down
- No domain verification blocking sends
- No complex query chains
2. Performance
- Skip database lookups
- Skip message tracking overhead
- Direct queue → SMTP
3. Security
- Configuration in environment (not user-controlled database)
- Can't be tampered with by customers
- Separate DKIM keys for brand
4. Simplicity
- Less code
- Fewer failure points
- Easier to debug
5. Scalability
- Separate queue = independent scaling
- Can scale system email worker independently
- No customer volume impact
What Gets Tracked
Tracked in Database:
- ✅ Password reset tokens (
PasswordResetTokentable) - ✅ User password changes (audit log)
- ✅ Session invalidation
NOT Tracked in Database:
- ❌ System email messages
- ❌ System email events
- ❌ System email delivery status
Tracked in Application Logs:
- ✅ Email queued (with RFC Message-ID)
- ✅ Email sent successfully
- ✅ Email failed (with error)
- ✅ SMTP response codes
Example: Password Reset Flow
// 1. User requests password reset
POST /v1/auth/forgot-password
{
"email": "user@example.com"
}
// 2. Service generates token (database)
await prisma.passwordResetToken.create({
token: "crypto-random-token",
userId: user.id,
expiresAt: oneHourFromNow
});
// 3. Service gets config from ENV (NOT database)
const systemDomain = {
domain: "postchi.io",
dkimSelector: process.env.SYSTEM_DKIM_SELECTOR,
dkimPrivateKey: process.env.SYSTEM_DKIM_PRIVATE_KEY
};
// 4. Queue email (NO database insert)
await systemEmailQueue.add("send-email", {
from: { email: "noreply@postchi.io", name: "Postchi" },
to: [user.email],
subject: "Reset Your Password",
html: emailTemplate,
dkimSelector: systemDomain.dkimSelector,
dkimPrivateKey: systemDomain.dkimPrivateKey
});
// 5. Worker sends directly (NO database tracking)
const transporter = createSmtpTransporter(dkimConfig);
await transporter.sendMail(mailOptions);
// 6. Log result (NO database insert)
logger.info({ email, smtpMessageId }, "Password reset email sent");
Comparison with Customer Emails
| Aspect | Customer Emails | System Emails |
|---|---|---|
| Domain Source | Database (Domain table) | Environment (SYSTEM_*) |
| Verification | Required (DNS + verification) | None |
| Organization | Customer's org ID | None |
| Message Tracking | Yes (Message table) | No |
| Event Tracking | Yes (MessageEvent table) | No |
| Queue | email-sending | system-email |
| Priority | Normal | Highest |
| Rate Limits | Yes | No |
| Sender | customer@customerdomain.com | noreply@postchi.io |
| DKIM Keys | Customer's keys (in DB) | System keys (in ENV) |
| Database Queries | 5-10 per email | 0 per email |
DNS Configuration
System emails require DNS configuration for postchi.io:
# SPF
postchi.io. TXT "v=spf1 ip4:YOUR_IP -all"
# DKIM
default._domainkey.postchi.io. TXT "v=DKIM1; k=rsa; p=YOUR_PUBLIC_KEY"
# DMARC
_dmarc.postchi.io. TXT "v=DMARC1; p=quarantine; ..."
See DNS-SETUP-POSTCHI-IO.md for complete instructions.
Future System Emails
This architecture supports all platform emails:
Already Implemented:
- ✅ Password reset
Easy to Add:
- 📧 Email verification (signup)
- 📧 Two-factor authentication codes
- 📧 Security alerts (suspicious login)
- 📧 Billing notifications
- 📧 Service announcements
- 📧 Account suspension notices
All follow the same pattern:
- Data in database (tokens, audit logs)
- Email config from ENV
- Queue to
systemEmailQueue - Worker sends directly
- No message tracking
Monitoring
Application Logs
All system emails are logged:
{
"level": "info",
"message": "Password reset email queued to system email queue",
"email": "user@example.com",
"rfcMessageId": "<1234567890.abc@postchi.io>"
}
{
"level": "info",
"message": "System email sent successfully",
"rfcMessageId": "<1234567890.abc@postchi.io>",
"smtpMessageId": "abc123@smtp.server",
"to": ["user@example.com"]
}
Queue Monitoring
Monitor system email queue health:
- Queue depth
- Processing rate
- Failed jobs
- Average latency
External Monitoring
- Google Postmaster Tools (reputation)
- Microsoft SNDS (reputation)
- Bounce rate tracking (via SMTP logs)
- Complaint rate tracking (via feedback loops)
Error Handling
System emails have robust error handling:
// In worker
try {
await transporter.sendMail(mailOptions);
logger.info({ ... }, "System email sent successfully");
return { success: true };
} catch (error) {
logger.error({ error, to: data.to }, "Failed to send system email");
throw error; // BullMQ will retry
}
Retry Strategy:
- 5 attempts (vs 3 for customer emails)
- Exponential backoff
- 1 second initial delay
Security Considerations
✅ Secure:
- DKIM keys in environment (not database)
- Can't be modified by customers
- Separate from customer keys
- Environment-only configuration
🛡️ Best Practices:
- Use dedicated IP for brand
- Monitor for abuse
- Rate limit forgot-password endpoint
- Use DMARC with strict alignment
- Rotate DKIM keys annually
Conclusion
System emails are platform infrastructure, not customer data. By keeping them separate from the customer email pipeline, we achieve:
- Higher reliability (fewer dependencies)
- Better performance (no database overhead)
- Stronger security (environment-only config)
- Simpler code (less complexity)
- Independent scaling (separate queue/worker)
This architecture prioritizes the critical nature of system emails while keeping customer email tracking robust and feature-rich.