Skip to main content

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:

  1. Independent - Not tied to any organization or domain in the database
  2. Reliable - Always work, even if database has issues
  3. Simple - No complex verification or tracking overhead
  4. Fast - Skip database lookups and validation
  5. 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 Domain record needed for postchi.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 (PasswordResetToken table)
  • ✅ 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​

AspectCustomer EmailsSystem Emails
Domain SourceDatabase (Domain table)Environment (SYSTEM_*)
VerificationRequired (DNS + verification)None
OrganizationCustomer's org IDNone
Message TrackingYes (Message table)No
Event TrackingYes (MessageEvent table)No
Queueemail-sendingsystem-email
PriorityNormalHighest
Rate LimitsYesNo
Sendercustomer@customerdomain.comnoreply@postchi.io
DKIM KeysCustomer's keys (in DB)System keys (in ENV)
Database Queries5-10 per email0 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:

  1. Data in database (tokens, audit logs)
  2. Email config from ENV
  3. Queue to systemEmailQueue
  4. Worker sends directly
  5. 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.