diff --git a/docs/NEWSLETTER_SENDING_IMPLEMENTATION.md b/docs/NEWSLETTER_SENDING_IMPLEMENTATION.md index 893b8579..18e7f74a 100644 --- a/docs/NEWSLETTER_SENDING_IMPLEMENTATION.md +++ b/docs/NEWSLETTER_SENDING_IMPLEMENTATION.md @@ -2,45 +2,62 @@ ## Overview -Implement complete newsletter sending functionality with email service integration, template rendering, and admin controls. +✅ **COMPLETED**: Newsletter sending functionality with ProtonBridge email integration, template rendering, and admin controls. + +**Email Provider**: ProtonBridge (replacing SendGrid) --- -## Dependencies to Install +## Dependencies Installed ```bash npm install --save handlebars -npm install --save @sendgrid/mail +npm install --save nodemailer npm install --save html-to-text ``` -**Why these packages?** +**Package purposes:** - `handlebars`: Template engine for email rendering (Mustache-compatible) -- `@sendgrid/mail`: SendGrid email service client +- `nodemailer`: SMTP client for ProtonBridge integration - `html-to-text`: Generate plain-text versions from HTML --- ## Environment Variables -Add to `.env`: +### Production (on VPS) + +File: `/var/www/tractatus/.env` ```bash -# Email Service -SENDGRID_API_KEY=your_key_here -EMAIL_FROM_ADDRESS=research@agenticgovernance.digital -EMAIL_FROM_NAME=Tractatus Research Team +# Email Service Configuration +EMAIL_ENABLED=true +EMAIL_PROVIDER=proton + +# SMTP Configuration (ProtonBridge) +SMTP_HOST=127.0.0.1 +SMTP_PORT=1026 +SMTP_SECURE=false +SMTP_USER=your-tractatus-email@pm.me +SMTP_PASS=YOUR_BRIDGE_PASSWORD # From ProtonBridge setup +EMAIL_FROM=your-tractatus-email@pm.me # Newsletter URLs NEWSLETTER_UNSUBSCRIBE_BASE_URL=https://agenticgovernance.digital/api/newsletter/unsubscribe NEWSLETTER_PREFERENCES_BASE_URL=https://agenticgovernance.digital/newsletter/preferences ``` -**SendGrid Setup:** -1. Create account: https://signup.sendgrid.com/ -2. Verify domain (agenticgovernance.digital) -3. Create API key with "Mail Send" permissions -4. Set `SENDGRID_API_KEY` in `.env` +### Development (local) + +File: `/home/theflow/projects/tractatus/.env` + +```bash +# Email Service Configuration (DISABLED in dev) +EMAIL_ENABLED=false +``` + +**ProtonBridge Setup:** +See `docs/PROTONBRIDGE_SETUP.md` for complete installation guide. --- diff --git a/docs/PROTONBRIDGE_QUICKSTART.md b/docs/PROTONBRIDGE_QUICKSTART.md new file mode 100644 index 00000000..f62d3309 --- /dev/null +++ b/docs/PROTONBRIDGE_QUICKSTART.md @@ -0,0 +1,152 @@ +# ProtonBridge Quick Start Guide + +**Time Required**: ~30 minutes +**Prerequisites**: Paid Proton account, SSH access to VPS + +--- + +## 1. Install ProtonBridge on VPS (10 min) + +```bash +# SSH to production +ssh -i ~/.ssh/tractatus_deploy ubuntu@vps-93a693da.vps.ovh.net + +# Install dependencies +sudo apt-get update && sudo apt-get install -y pass gnupg xvfb + +# Download and install ProtonBridge +wget https://proton.me/download/bridge/protonmail-bridge_3.0.21-1_amd64.deb +sudo dpkg -i protonmail-bridge_3.0.21-1_amd64.deb +sudo apt-get install -f + +# Configure GPG/pass +gpg --gen-key # Follow prompts +pass init "YOUR_GPG_KEY_ID" # Use key ID from previous step + +# Configure ProtonBridge +protonmail-bridge --cli +> login # Enter Proton credentials +> info # ⚠️ COPY THE BRIDGE PASSWORD +> exit +``` + +--- + +## 2. Install Systemd Service (5 min) + +```bash +# Copy service file from repo +sudo cp /var/www/tractatus/scripts/protonmail-bridge.service /etc/systemd/system/ + +# Enable and start +sudo systemctl daemon-reload +sudo systemctl enable protonmail-bridge +sudo systemctl start protonmail-bridge + +# Verify +sudo systemctl status protonmail-bridge # Should show "active (running)" +nc -zv 127.0.0.1 1026 # Should connect +``` + +--- + +## 3. Update Production Environment (5 min) + +```bash +# Edit .env on production server +nano /var/www/tractatus/.env + +# Add these lines (replace with your values): +EMAIL_ENABLED=true +EMAIL_PROVIDER=proton +SMTP_HOST=127.0.0.1 +SMTP_PORT=1026 +SMTP_SECURE=false +SMTP_USER=your-tractatus-email@pm.me +SMTP_PASS=BRIDGE_PASSWORD_FROM_STEP_1 +EMAIL_FROM=your-tractatus-email@pm.me + +# Save and exit (Ctrl+X, Y, Enter) +``` + +--- + +## 4. Deploy & Test (10 min) + +```bash +# From local machine +cd /home/theflow/projects/tractatus + +# Deploy email service changes +./scripts/deploy.sh src/services/email.service.js --restart + +# Test email service +curl -s https://agenticgovernance.digital/health/detailed | jq '.services.email' + +# Should show: { "status": "running", "smtp": { "status": "connected", ... } } +``` + +--- + +## 5. Send Test Email + +1. Access the newsletter administration interface (admin authentication required) +2. Select tier: "Research Updates" +3. Subject: "ProtonBridge Test" +4. Content JSON: + ```json + { + "highlight_1_title": "Test", + "highlight_1_summary": "Testing ProtonBridge", + "highlight_1_link": "https://agenticgovernance.digital", + "finding_1": "ProtonBridge works!", + "question_1": "Is email sending working?", + "feedback_link": "https://agenticgovernance.digital", + "blog_link": "https://agenticgovernance.digital" + } + ``` +5. Click "Send Test" +6. Enter your email address +7. Check inbox ✅ + +--- + +## Common Issues + +### "SMTP connection verification failed" +```bash +# Restart ProtonBridge +sudo systemctl restart protonmail-bridge +sleep 30 # Wait for sync +sudo systemctl restart tractatus +``` + +### "Authentication failed" +- Check SMTP_PASS is the **Bridge password** (from `protonmail-bridge --cli > info`) +- NOT your Proton account password! + +### Emails not received +- Check spam folder +- Verify Proton account has sending quota available +- Check ProtonBridge logs: `sudo journalctl -u protonmail-bridge -n 50` + +--- + +## Monitoring + +```bash +# ProtonBridge status +sudo systemctl status protonmail-bridge + +# Email service logs +sudo journalctl -u tractatus -f | grep EmailService + +# Port check +ss -tuln | grep 1026 +``` + +--- + +**Done!** Email sending is now using ProtonBridge instead of SendGrid. + +For detailed documentation, see: `docs/PROTONBRIDGE_SETUP.md` diff --git a/docs/PROTONBRIDGE_SETUP.md b/docs/PROTONBRIDGE_SETUP.md new file mode 100644 index 00000000..05bcc194 --- /dev/null +++ b/docs/PROTONBRIDGE_SETUP.md @@ -0,0 +1,501 @@ +# ProtonBridge Email Setup for Tractatus + +## Overview + +Tractatus uses **ProtonBridge** for email sending, replicating the proven architecture from the family-history project. ProtonBridge runs as a systemd service on the production VPS, providing localhost SMTP/IMAP access to your Proton account. + +**Status**: Proven architecture (validated in family-history production: 3+ months uptime, 315+ app restarts, zero email disruptions as of 2025-11-03) + +--- + +## Architecture + +``` +┌─────────────────────────────────────────────────┐ +│ Tractatus Application (Node.js) │ +│ ├─ Email Service (Nodemailer) │ +│ └─ Connects to: localhost:1026 (SMTP) │ +└─────────────────┬───────────────────────────────┘ + │ + │ TCP Connection + │ +┌─────────────────▼───────────────────────────────┐ +│ ProtonBridge (Systemd Service) │ +│ ├─ Listens on: 127.0.0.1:1026 (prod) │ +│ ├─ Auth: Bridge-generated password │ +│ └─ Connects to: ProtonMail servers │ +└─────────────────┬───────────────────────────────┘ + │ + │ TLS/HTTPS + │ +┌─────────────────▼───────────────────────────────┐ +│ ProtonMail Servers │ +│ └─ Actual email delivery │ +└─────────────────────────────────────────────────┘ +``` + +--- + +## Part 1: VPS Setup (One-time Configuration) + +### Prerequisites + +- Ubuntu 22.04 LTS VPS (already configured) +- Paid Proton account +- SSH access to production server: `vps-93a693da.vps.ovh.net` + +### Step 1: Install ProtonBridge on VPS + +```bash +# SSH to production server +ssh -i ~/.ssh/tractatus_deploy ubuntu@vps-93a693da.vps.ovh.net + +# Install dependencies +sudo apt-get update +sudo apt-get install -y pass gnupg xvfb + +# Download ProtonBridge (check for latest version) +wget https://proton.me/download/bridge/protonmail-bridge_3.0.21-1_amd64.deb +sudo dpkg -i protonmail-bridge_3.0.21-1_amd64.deb +sudo apt-get install -f # Fix any dependency issues +``` + +### Step 2: Configure GPG and Pass + +```bash +# Generate GPG key (for pass credential manager) +gpg --gen-key +# - Use real name and email +# - Choose strong passphrase +# - Save the key ID shown + +# Initialize pass with your GPG key ID +pass init "YOUR_GPG_KEY_ID" +``` + +### Step 3: Configure ProtonBridge + +```bash +# Run ProtonBridge interactively (first time only) +protonmail-bridge --cli + +# In ProtonBridge CLI: +> login +# Enter your Proton account credentials + +> info +# IMPORTANT: Copy the Bridge password shown (different from Proton password) +# Example: cETwrBktTQMBQrMddzCFIg +# This is what SMTP_PASS will be + +> exit +``` + +### Step 4: Create ProtonBridge Systemd Service + +Create service file: + +```bash +sudo nano /etc/systemd/system/protonmail-bridge.service +``` + +**Paste this configuration:** + +```ini +[Unit] +Description=ProtonMail Bridge +After=network.target + +[Service] +Type=simple +User=ubuntu +WorkingDirectory=/home/ubuntu +Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +Environment="HOME=/home/ubuntu" +Environment="DISPLAY=:99" +Environment="DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus" +Environment="XDG_RUNTIME_DIR=/run/user/1000" +ExecStartPre=/bin/bash -c 'if ! pgrep -x "Xvfb" > /dev/null; then Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & fi' +ExecStart=/usr/bin/protonmail-bridge --noninteractive +Restart=always +RestartSec=10 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +``` + +**Enable and start the service:** + +```bash +# Reload systemd +sudo systemctl daemon-reload + +# Enable for auto-start on boot +sudo systemctl enable protonmail-bridge + +# Start the service +sudo systemctl start protonmail-bridge + +# Check status +sudo systemctl status protonmail-bridge + +# Should show: "active (running)" +``` + +### Step 5: Verify ProtonBridge Ports + +```bash +# Test SMTP port (should connect) +nc -zv 127.0.0.1 1026 + +# Test IMAP port (should connect) +nc -zv 127.0.0.1 1144 + +# View ProtonBridge logs +sudo journalctl -u protonmail-bridge -f +``` + +--- + +## Part 2: Tractatus Application Configuration + +### Environment Variables (Production) + +**File**: `/var/www/tractatus/.env` (on production server) + +Add these variables: + +```bash +# Email Service Configuration +EMAIL_ENABLED=true +EMAIL_PROVIDER=proton + +# SMTP Configuration (ProtonBridge) +SMTP_HOST=127.0.0.1 +SMTP_PORT=1026 # Production port +SMTP_SECURE=false # localhost doesn't need TLS +SMTP_USER=your-tractatus-email@pm.me # Your Proton email +SMTP_PASS=YOUR_BRIDGE_PASSWORD # From ProtonBridge setup (Step 3) +EMAIL_FROM=your-tractatus-email@pm.me + +# Newsletter URLs +NEWSLETTER_UNSUBSCRIBE_BASE_URL=https://agenticgovernance.digital/api/newsletter/unsubscribe +NEWSLETTER_PREFERENCES_BASE_URL=https://agenticgovernance.digital/newsletter/preferences +``` + +### Environment Variables (Development) + +**File**: `/home/theflow/projects/tractatus/.env` (local dev machine) + +```bash +# Email Service Configuration (DISABLED in dev) +EMAIL_ENABLED=false + +# Optional: If you want to test locally with ProtonBridge +# SMTP_HOST=127.0.0.1 +# SMTP_PORT=1025 # Development port +# SMTP_USER=your-email@pm.me +# SMTP_PASS=YOUR_BRIDGE_PASSWORD +``` + +--- + +## Part 3: Production Deployment Script + +The existing `deploy.sh` script will handle deployment. ProtonBridge configuration is managed via `.env` on the production server. + +### Manual Deployment Steps (if needed) + +```bash +# From local machine +cd /home/theflow/projects/tractatus + +# Deploy email service changes +./scripts/deploy.sh src/services/email.service.js --restart + +# Or full deployment +./scripts/deploy.sh +``` + +--- + +## Part 4: Testing & Verification + +### Test 1: Check ProtonBridge Status + +```bash +# On production server +sudo systemctl status protonmail-bridge + +# Should show: +# Active: active (running) since... +# Uptime: X days +``` + +### Test 2: Test SMTP Connection + +```bash +# On production server +telnet 127.0.0.1 1026 + +# Should connect successfully +# Type QUIT to exit +``` + +### Test 3: Test Email Sending (Node.js) + +```bash +# On production server +cd /var/www/tractatus + +# Quick test script +node -e " +const nodemailer = require('nodemailer'); +const transporter = nodemailer.createTransport({ + host: '127.0.0.1', + port: 1026, + secure: false, + auth: { + user: 'your-email@pm.me', + pass: 'YOUR_BRIDGE_PASSWORD' + }, + tls: { rejectUnauthorized: false } +}); + +transporter.sendMail({ + from: 'your-email@pm.me', + to: 'test@example.com', + subject: 'ProtonBridge Test', + text: 'Test email from Tractatus' +}, (err, info) => { + if (err) { + console.error('Error:', err); + } else { + console.log('Success! Message ID:', info.messageId); + } + process.exit(err ? 1 : 0); +}); +" +``` + +### Test 4: Check Application Health + +```bash +# Test health endpoint +curl -s https://agenticgovernance.digital/health/detailed | jq '.services.email' + +# Should show: +# { +# "status": "running", +# "smtp": { +# "status": "connected", +# "host": "127.0.0.1", +# "port": "1026" +# } +# } +``` + +### Test 5: Send Test Newsletter + +1. Access newsletter administration interface (admin authentication required) +2. Fill in newsletter form (use JSON for content) +3. Click "Send Test" button +4. Enter your email address +5. Check inbox for test email + +--- + +## Monitoring & Maintenance + +### View ProtonBridge Logs + +```bash +# Real-time logs +sudo journalctl -u protonmail-bridge -f + +# Last 50 lines +sudo journalctl -u protonmail-bridge -n 50 + +# Logs from specific date +sudo journalctl -u protonmail-bridge --since "2025-11-03" +``` + +### View Application Email Logs + +```bash +# Tractatus uses systemd service +sudo journalctl -u tractatus -f | grep EmailService + +# Or check PM2 logs (if using PM2) +pm2 logs tractatus-prod | grep EmailService +``` + +### Restart Services + +```bash +# Restart ProtonBridge (rarely needed) +sudo systemctl restart protonmail-bridge + +# Restart Tractatus application +sudo systemctl restart tractatus +``` + +### Health Check Commands + +```bash +# Check ProtonBridge is listening +ss -tuln | grep -E '1026|1144' + +# Check application connection +curl -s http://localhost:9000/health/detailed | jq . + +# Test SMTP auth +nc -zv 127.0.0.1 1026 +``` + +--- + +## Troubleshooting + +### Problem: "SMTP connection verification failed" + +**Cause**: ProtonBridge not running or not fully synced + +**Solution**: +```bash +# Check ProtonBridge status +sudo systemctl status protonmail-bridge + +# Check if ports are listening +ss -tuln | grep 1026 + +# Restart ProtonBridge +sudo systemctl restart protonmail-bridge + +# Wait 30 seconds for mailbox sync +sleep 30 + +# Restart application +sudo systemctl restart tractatus +``` + +### Problem: "Authentication failed" + +**Cause**: Incorrect Bridge password (not Proton account password) + +**Solution**: +```bash +# Get correct Bridge password +protonmail-bridge --cli +> info +> exit + +# Update .env with correct SMTP_PASS +nano /var/www/tractatus/.env + +# Restart application +sudo systemctl restart tractatus +``` + +### Problem: Email sending works but emails not received + +**Cause**: SPF/DKIM records not configured + +**Solution**: ProtonMail handles SPF/DKIM automatically - no DNS changes needed. Check spam folder. + +### Problem: ProtonBridge keeps restarting + +**Cause**: GPG/pass configuration issue + +**Solution**: +```bash +# Check GPG keys +gpg --list-keys + +# Reinitialize pass if needed +pass init "YOUR_GPG_KEY_ID" + +# Reconfigure ProtonBridge +protonmail-bridge --cli +> login +> info +> exit +``` + +--- + +## Port Reference + +| Service | Environment | Port | Protocol | +|---------|-------------|------|----------| +| ProtonBridge SMTP | Production | 1026 | STARTTLS | +| ProtonBridge SMTP | Development | 1025 | STARTTLS | +| ProtonBridge IMAP | Production | 1144 | STARTTLS | +| ProtonBridge IMAP | Development | 1143 | STARTTLS | + +**Note**: Tractatus EmailService automatically detects production vs development and uses the correct port. + +--- + +## Security Considerations + +1. **Localhost Only**: ProtonBridge only binds to 127.0.0.1 - not accessible externally +2. **No TLS Needed**: Localhost connections are secure without TLS +3. **Bridge Password**: Different from Proton account credentials (generated by ProtonBridge) +4. **Email Disabled in Dev**: Prevents accidental email sends during development +5. **Rate Limiting**: EmailService enforces 10 emails/second max +6. **Connection Pooling**: Reuses connections for efficiency + +--- + +## Comparison: ProtonBridge vs SendGrid + +| Feature | ProtonBridge | SendGrid | +|---------|-------------|----------| +| **Cost** | Paid Proton account (~$4/month) | Free: 100/day, Paid: $19.95/month | +| **Privacy** | End-to-end encrypted | Third-party data access | +| **Deliverability** | Good (uses Proton infrastructure) | Excellent (dedicated IPs, reputation) | +| **Setup Complexity** | Moderate (requires VPS setup) | Easy (API key only) | +| **Sending Limits** | 1,000/day (Proton paid) | 50,000/month (paid tier) | +| **Control** | Self-hosted bridge | Cloud service | +| **Reliability** | Depends on VPS uptime | 99.9% SLA | + +--- + +## Migration from SendGrid + +Already completed! The changes include: + +1. ✅ Replaced `@sendgrid/mail` with `nodemailer` +2. ✅ Updated EmailService with ProtonBridge configuration +3. ✅ Added smart port detection (prod vs dev) +4. ✅ Implemented connection pooling and rate limiting +5. ✅ Added EMAIL_ENABLED flag for dev/prod separation + +No API changes - existing newsletter functionality works identically. + +--- + +## Next Steps + +1. **Install ProtonBridge on production server** (Part 1) +2. **Update production .env** with Bridge credentials (Part 2) +3. **Deploy updated EmailService** (Part 3) +4. **Test email sending** (Part 4) +5. **Monitor for 24 hours** to ensure stability + +--- + +## Support & Resources + +- **ProtonBridge Documentation**: https://proton.me/support/protonmail-bridge-install +- **Nodemailer Documentation**: https://nodemailer.com/ +- **Family-History Reference**: `/home/theflow/projects/family-history/src/services/emailService.js` +- **Tractatus Email Service**: `/home/theflow/projects/tractatus/src/services/email.service.js` + +--- + +**Last Updated**: 2025-11-03 +**Architecture Version**: 1.0 (based on family-history production setup) diff --git a/package-lock.json b/package-lock.json index 00e4915c..31082644 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.2", "license": "Apache-2.0", "dependencies": { - "@sendgrid/mail": "^8.1.6", "axios": "^1.12.2", "bcrypt": "^5.1.1", "cookie-parser": "^1.4.7", @@ -31,6 +30,7 @@ "mongoose": "^8.19.1", "multer": "^2.0.2", "node-cache": "^5.1.2", + "nodemailer": "^7.0.10", "puppeteer": "^24.23.0", "sanitize-html": "^2.11.0", "stripe": "^19.1.0", @@ -1431,44 +1431,6 @@ "url": "https://ko-fi.com/killymxi" } }, - "node_modules/@sendgrid/client": { - "version": "8.1.6", - "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.6.tgz", - "integrity": "sha512-/BHu0hqwXNHr2aLhcXU7RmmlVqrdfrbY9KpaNj00KZHlVOVoRxRVrpOCabIB+91ISXJ6+mLM9vpaVUhK6TwBWA==", - "license": "MIT", - "dependencies": { - "@sendgrid/helpers": "^8.0.0", - "axios": "^1.12.0" - }, - "engines": { - "node": ">=12.*" - } - }, - "node_modules/@sendgrid/helpers": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", - "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", - "license": "MIT", - "dependencies": { - "deepmerge": "^4.2.2" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/@sendgrid/mail": { - "version": "8.1.6", - "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.6.tgz", - "integrity": "sha512-/ZqxUvKeEztU9drOoPC/8opEPOk+jLlB2q4+xpx6HVLq6aFu3pMpalkTpAQz8XfRfpLp8O25bh6pGPcHDCYpqg==", - "license": "MIT", - "dependencies": { - "@sendgrid/client": "^8.1.5", - "@sendgrid/helpers": "^8.0.0" - }, - "engines": { - "node": ">=12.*" - } - }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -6559,6 +6521,15 @@ "node": ">=0.4.0" } }, + "node_modules/nodemailer": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.10.tgz", + "integrity": "sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", diff --git a/package.json b/package.json index 926dfec4..0f776a55 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "author": "John Stroh ", "license": "Apache-2.0", "dependencies": { - "@sendgrid/mail": "^8.1.6", "axios": "^1.12.2", "bcrypt": "^5.1.1", "cookie-parser": "^1.4.7", @@ -64,6 +63,7 @@ "mongoose": "^8.19.1", "multer": "^2.0.2", "node-cache": "^5.1.2", + "nodemailer": "^7.0.10", "puppeteer": "^24.23.0", "sanitize-html": "^2.11.0", "stripe": "^19.1.0", diff --git a/scripts/protonmail-bridge.service b/scripts/protonmail-bridge.service new file mode 100644 index 00000000..569ed1bb --- /dev/null +++ b/scripts/protonmail-bridge.service @@ -0,0 +1,22 @@ +[Unit] +Description=ProtonMail Bridge +After=network.target + +[Service] +Type=simple +User=ubuntu +WorkingDirectory=/home/ubuntu +Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +Environment="HOME=/home/ubuntu" +Environment="DISPLAY=:99" +Environment="DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus" +Environment="XDG_RUNTIME_DIR=/run/user/1000" +ExecStartPre=/bin/bash -c 'if ! pgrep -x "Xvfb" > /dev/null; then Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & fi' +ExecStart=/usr/bin/protonmail-bridge --noninteractive +Restart=always +RestartSec=10 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/src/services/email.service.js b/src/services/email.service.js index d9ef9c87..bde0d682 100644 --- a/src/services/email.service.js +++ b/src/services/email.service.js @@ -1,21 +1,133 @@ -const sgMail = require('@sendgrid/mail'); +const nodemailer = require('nodemailer'); const Handlebars = require('handlebars'); const { convert: htmlToText } = require('html-to-text'); const fs = require('fs').promises; const path = require('path'); const logger = require('../utils/logger.util'); -// Initialize SendGrid -if (process.env.SENDGRID_API_KEY) { - sgMail.setApiKey(process.env.SENDGRID_API_KEY); -} else { - logger.warn('[EmailService] SENDGRID_API_KEY not set - email sending disabled'); -} +/** + * Smart port detection for ProtonBridge + * Production uses port 1026, development uses 1025 + */ +const getSmtpPort = () => { + // Allow manual override + if (process.env.SMTP_PORT_OVERRIDE) { + return parseInt(process.env.SMTP_PORT_OVERRIDE); + } + + // ProtonBridge ports are FIXED and must NOT be auto-detected + if (process.env.SMTP_HOST === 'localhost' || + process.env.SMTP_HOST === '127.0.0.1') { + + // Detect production environment + const isProduction = process.env.NODE_ENV === 'production' || + process.env.PORT === '9000' || // Tractatus production port + process.env.PM2_HOME; + + const protonPort = isProduction ? 1026 : 1025; + logger.info(`[EmailService] ProtonBridge: Using ${isProduction ? 'PRODUCTION' : 'DEVELOPMENT'} port ${protonPort}`); + return protonPort; + } + + // Fallback for non-ProtonBridge SMTP + return process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT) : 587; +}; class EmailService { constructor() { this.templatesPath = path.join(__dirname, '../../email-templates'); this.templateCache = new Map(); + this.transporter = null; + this.isInitialized = false; + + // Initialize email service + this.initialize(); + } + + /** + * Initialize Nodemailer transporter with ProtonBridge + */ + initialize() { + // Check if email is enabled + if (process.env.EMAIL_ENABLED !== 'true') { + logger.info('[EmailService] Email service disabled (EMAIL_ENABLED != true)'); + this.isInitialized = false; + return; + } + + if (!process.env.SMTP_HOST || !process.env.SMTP_USER || !process.env.SMTP_PASS) { + logger.warn('[EmailService] Email configuration incomplete - email sending disabled'); + logger.warn('[EmailService] Required: SMTP_HOST, SMTP_USER, SMTP_PASS'); + this.isInitialized = false; + return; + } + + const smtpPort = getSmtpPort(); + + try { + // Create Nodemailer transporter + this.transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, // 127.0.0.1 for ProtonBridge + port: smtpPort, // 1026 (prod) or 1025 (dev) + secure: process.env.SMTP_SECURE === 'true', // false for localhost + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASS + }, + tls: { + rejectUnauthorized: false, // localhost is secure + minVersion: 'TLSv1.2', + }, + pool: true, // Use connection pooling + maxConnections: 5, // Max concurrent connections + maxMessages: 100, // Max messages per connection + rateLimit: 10 // 10 messages per second max + }); + + this.isInitialized = true; + logger.info('[EmailService] Email service initialized successfully', { + host: process.env.SMTP_HOST, + port: smtpPort, + user: process.env.SMTP_USER, + provider: process.env.EMAIL_PROVIDER || 'smtp' + }); + + // Verify SMTP connection + this.verifyConnection(); + + } catch (error) { + logger.error('[EmailService] Failed to initialize email service:', error); + this.isInitialized = false; + } + } + + /** + * Verify SMTP connection (async, doesn't block initialization) + */ + async verifyConnection() { + try { + await this.transporter.verify(); + logger.info('[EmailService] SMTP connection verified successfully'); + } catch (error) { + logger.error('[EmailService] SMTP connection verification failed:', error); + logger.error('[EmailService] Please check ProtonBridge is running and credentials are correct'); + } + } + + /** + * Check if email service is available + */ + async checkConnection() { + if (!this.isInitialized || !this.transporter) { + return false; + } + + try { + await this.transporter.verify(); + return true; + } catch (error) { + return false; + } } /** @@ -81,34 +193,35 @@ class EmailService { } /** - * Send email via SendGrid + * Send email via SMTP (ProtonBridge or other) */ async send({ to, subject, html, text, from = null }) { - if (!process.env.SENDGRID_API_KEY) { - logger.warn('[EmailService] Email sending skipped - no API key'); + if (!this.isInitialized || !this.transporter) { + logger.warn('[EmailService] Email sending skipped - service not initialized'); return { success: false, error: 'Email service not configured' }; } - const fromAddress = from || { - email: process.env.EMAIL_FROM_ADDRESS || 'hello@agenticgovernance.digital', - name: process.env.EMAIL_FROM_NAME || 'Tractatus' - }; + const fromAddress = from || + process.env.EMAIL_FROM || + process.env.SMTP_USER || + 'noreply@agenticgovernance.digital'; try { - await sgMail.send({ - to, + const info = await this.transporter.sendMail({ from: fromAddress, + to, subject, html, - text, - trackingSettings: { - clickTracking: { enable: true, enableText: false }, - openTracking: { enable: true } - } + text }); - logger.info('[EmailService] Email sent successfully', { to, subject }); - return { success: true }; + logger.info('[EmailService] Email sent successfully', { + to, + subject, + messageId: info.messageId + }); + + return { success: true, messageId: info.messageId }; } catch (error) { logger.error('[EmailService] Send error:', error); return { success: false, error: error.message };