1336 lines
27 KiB
Markdown

# Email and SMTP Issues
This guide covers email sending, SMTP configuration, and template-related problems in Changemaker Lite V2.
## Overview
### Email System Architecture
Changemaker Lite V2 has **dual email systems**:
1. **Transactional Emails** (BullMQ + Nodemailer)
- Campaign advocacy emails
- Shift confirmation emails
- Response verification emails
- System notifications
2. **Newsletter Emails** (Listmonk)
- Marketing campaigns
- Newsletter broadcasts
- Subscriber management
### Email Flow
```
User Action → Email Service → BullMQ Queue → Worker → SMTP Server → Recipient
```
### Key Components
- **BullMQ** - Job queue for async email sending
- **Nodemailer** - SMTP client library
- **Redis** - Queue backend
- **MailHog** - Development email capture (test mode)
- **Listmonk** - Newsletter platform (optional)
---
## SMTP Configuration
### Connection Refused
**Severity:** 🔴 Critical
#### Symptoms
API logs:
```
Error: Connection timeout
Error: connect ECONNREFUSED smtp.gmail.com:587
Error: Invalid login: 535-5.7.8 Username and Password not accepted
```
Emails not sending.
#### Common Causes
1. **Wrong SMTP host** - Incorrect hostname
2. **Port blocked** - Firewall blocking port 587/465
3. **Wrong credentials** - Invalid username/password
4. **TLS/SSL mismatch** - Wrong secure setting
#### Solutions
**Solution 1: Test SMTP connection**
```bash
# Test with telnet
telnet smtp.gmail.com 587
# Should show:
# 220 smtp.gmail.com ESMTP ...
# Or test with openssl (for SSL)
openssl s_client -connect smtp.gmail.com:465
```
**Solution 2: Verify SMTP configuration**
In `.env`:
```bash
# Gmail example (requires app password)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false # false for STARTTLS on 587, true for SSL on 465
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password # NOT regular password
SMTP_FROM=your-email@gmail.com
# Office365 example
SMTP_HOST=smtp.office365.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=your-email@outlook.com
SMTP_PASS=your-password
SMTP_FROM=your-email@outlook.com
# SendGrid example
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=apikey # Literally "apikey"
SMTP_PASS=your-sendgrid-api-key
SMTP_FROM=your-verified-sender@example.com
```
**Solution 3: Use test mode**
```bash
# In .env
EMAIL_TEST_MODE=true
# Restart API
docker compose restart api
# All emails now sent to MailHog
# View at http://localhost:8025
```
**Solution 4: Test email sending**
```bash
# Send test email via API
curl -X POST http://localhost:4000/api/test-email \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"to": "test@example.com",
"subject": "Test Email",
"text": "This is a test email from Changemaker Lite"
}'
# Check API logs
docker compose logs api | grep -i "email\|smtp"
```
**Solution 5: Gmail app password**
For Gmail (required if 2FA enabled):
1. Go to https://myaccount.google.com/apppasswords
2. Select app: Mail
3. Select device: Other (Changemaker Lite)
4. Click Generate
5. Copy 16-character password
6. Use in SMTP_PASS (no spaces)
#### Prevention
- **Test mode for dev** - Use MailHog locally
- **Secure credentials** - Use app passwords, not real passwords
- **Environment-specific** - Different SMTP per environment
- **Health checks** - Test SMTP on API startup
---
### Authentication Failed
**Severity:** 🔴 Critical
#### Symptoms
```
Error: Invalid login: 535-5.7.8 Username and Password not accepted
Error: 535 Authentication failed
```
#### Common Causes
1. **Wrong password** - Incorrect password
2. **2FA enabled** - Need app password
3. **Less secure apps** - Gmail blocking
4. **Account locked** - Too many failed attempts
#### Solutions
**Solution 1: Verify credentials**
```bash
# Check .env
cat .env | grep SMTP_
# Test login manually (if possible)
# Gmail doesn't allow this, but some SMTP servers do
```
**Solution 2: Enable less secure apps (Gmail)**
⚠️ Not recommended. Use app password instead.
1. Go to https://myaccount.google.com/lesssecureapps
2. Turn on "Allow less secure apps"
**Solution 3: Check account status**
1. Try logging into email account via web
2. Check for security alerts
3. Verify account not locked
**Solution 4: Use OAuth2 (advanced)**
For production Gmail:
```typescript
// In email.service.ts
const transporter = nodemailer.createTransporter({
service: 'gmail',
auth: {
type: 'OAuth2',
user: process.env.SMTP_USER,
clientId: process.env.GMAIL_CLIENT_ID,
clientSecret: process.env.GMAIL_CLIENT_SECRET,
refreshToken: process.env.GMAIL_REFRESH_TOKEN
}
});
```
#### Prevention
- **App passwords** - Always use app-specific passwords
- **Test credentials** - Verify before deploying
- **Monitor failures** - Alert on auth failures
- **Backup SMTP** - Configure fallback SMTP server
---
### Invalid Credentials
**Severity:** 🔴 Critical
#### Symptoms
```
Error: Invalid SMTP credentials
Error: Username and Password not accepted
```
#### Solutions
See "Authentication Failed" section above.
---
### Port Blocked
**Severity:** 🟠 High
#### Symptoms
```
Error: connect ETIMEDOUT smtp.gmail.com:587
Error: Connection timeout
```
Connection attempt hangs, then times out after 30+ seconds.
#### Common Causes
1. **Firewall blocking** - Network firewall blocking port
2. **ISP blocking** - ISP blocks port 25/587
3. **Docker network** - Container can't reach external SMTP
#### Solutions
**Solution 1: Test port access**
```bash
# From API container
docker compose exec api telnet smtp.gmail.com 587
# If timeout, port is blocked
```
**Solution 2: Try alternative port**
```bash
# Try port 465 (SSL) instead of 587 (STARTTLS)
SMTP_PORT=465
SMTP_SECURE=true
# Or try port 2525 (some providers)
SMTP_PORT=2525
SMTP_SECURE=false
```
**Solution 3: Check Docker network**
```bash
# Test external connectivity
docker compose exec api ping -c 3 smtp.gmail.com
# Test DNS resolution
docker compose exec api nslookup smtp.gmail.com
# If fails, Docker network issue
```
**Solution 4: Use SMTP relay**
If ISP blocks SMTP, use relay service:
- SendGrid
- Mailgun
- Amazon SES
- Postmark
**Solution 5: VPN or proxy**
As last resort, route SMTP through VPN/proxy.
#### Prevention
- **Use relay services** - More reliable than direct SMTP
- **Multiple ports** - Try 587, 465, 2525
- **Test on deploy** - Verify SMTP works in production
- **Documentation** - Document network requirements
---
## Template Issues
### Template Not Found
**Severity:** 🟠 High
#### Symptoms
API logs:
```
Error: Email template not found: campaign-email
Error: ENOENT: no such file or directory, open 'templates/campaign-email.html'
```
#### Common Causes
1. **Template file missing** - File doesn't exist
2. **Wrong template name** - Typo in name
3. **Wrong directory** - Looking in wrong path
4. **Deleted template** - Template was removed
#### Solutions
**Solution 1: List available templates**
```bash
# List template files
docker compose exec api ls -la templates/
# Should show:
# campaign-email.html
# shift-confirmation.html
# verification-email.html
# response-verification.html
```
**Solution 2: Create missing template**
```bash
# Create template file
docker compose exec api sh -c 'cat > templates/my-template.html << EOF
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<h1>Hello {{name}}</h1>
<p>{{message}}</p>
</body>
</html>
EOF'
```
**Solution 3: Use email template system**
Navigate to `/app/email-templates`:
1. Click "Create Template"
2. Fill in details
3. Design template
4. Save (creates file + DB record)
**Solution 4: Check template name**
```typescript
// In code, template name must match filename (without .html)
await emailService.sendEmail({
to: email,
subject: 'Campaign Email',
template: 'campaign-email', // Looks for templates/campaign-email.html
variables: { ... }
});
```
**Solution 5: Verify template path**
In `api/src/services/email.service.ts`:
```typescript
const templatePath = path.join(__dirname, '../../templates', `${template}.html`);
// Resolves to: api/templates/campaign-email.html
```
#### Prevention
- **Seed templates** - Include default templates in seed
- **Template management** - Use admin UI to manage
- **Version control** - Keep templates in git
- **Validation** - Check template exists before sending
---
### Variable Not Replaced
**Severity:** 🟡 Medium
#### Symptoms
Email received with unreplaced placeholders:
```
Hello {{name}},
Your campaign {{campaignName}} is ready.
```
#### Common Causes
1. **Variable not provided** - Missing from variables object
2. **Typo in variable name** - Mismatch between template and code
3. **Wrong delimiter** - Using ${} instead of {{}}
4. **Escaping issue** - HTML entities interfering
#### Solutions
**Solution 1: List template variables**
```bash
# Find all variables in template
docker compose exec api grep -o '{{[^}]*}}' templates/campaign-email.html
# Shows:
# {{name}}
# {{campaignName}}
# {{campaignUrl}}
```
**Solution 2: Provide all variables**
```typescript
await emailService.sendEmail({
to: email,
subject: 'Campaign Ready',
template: 'campaign-email',
variables: {
name: user.name, // Must provide ALL variables in template
campaignName: campaign.name,
campaignUrl: `${process.env.PUBLIC_URL}/campaigns/${campaign.id}`
}
});
```
**Solution 3: Check variable delimiter**
```html
<!-- Correct (Handlebars-style) -->
<h1>Hello {{name}}</h1>
<p>Your campaign {{campaignName}} is ready.</p>
<!-- Wrong -->
<h1>Hello ${name}</h1> <!-- JavaScript template literal -->
<p>Your campaign {campaignName} is ready.</p> <!-- Single braces -->
```
**Solution 4: Test template rendering**
```bash
# Test template rendering
curl -X POST http://localhost:4000/api/test-template \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"template": "campaign-email",
"variables": {
"name": "John",
"campaignName": "Save the Planet",
"campaignUrl": "https://example.com/campaigns/123"
}
}'
# Returns rendered HTML
```
**Solution 5: Use default values**
```html
<!-- In template, provide fallback -->
<h1>Hello {{name || "Friend"}}</h1>
```
Or in code:
```typescript
const variables = {
name: user.name || 'Friend',
campaignName: campaign.name || 'Campaign',
campaignUrl: campaignUrl || '#'
};
```
#### Prevention
- **Template validation** - Check all variables exist
- **TypeScript types** - Type template variables
- **Default values** - Always provide defaults
- **Testing** - Test all templates with sample data
---
### Syntax Errors
**Severity:** 🟠 High
#### Symptoms
```
Error: Parse error in template at line 15
Error: Unexpected token in template
```
Email fails to send.
#### Common Causes
1. **Invalid HTML** - Malformed HTML
2. **Unclosed tags** - Missing closing tags
3. **Special characters** - Unescaped < > &
4. **Handlebars syntax** - Invalid {{}} usage
#### Solutions
**Solution 1: Validate HTML**
```bash
# Use HTML validator
# Copy template content to https://validator.w3.org/nu/
# Or validate locally
docker compose exec api npx html-validate templates/campaign-email.html
```
**Solution 2: Check common errors**
```html
<!-- Unclosed tag -->
<div>Content here
<!-- Should be: -->
<div>Content here</div>
<!-- Unescaped characters -->
Price: $50 < $100
<!-- Should be: -->
Price: $50 &lt; $100
<!-- Invalid Handlebars -->
{{if name}} <!-- No "if" helper by default -->
<!-- Should be: -->
{{#if name}}...{{/if}} <!-- Or don't use if -->
```
**Solution 3: Escape HTML**
```typescript
// In email.service.ts
import handlebars from 'handlebars';
// Register escape helper
handlebars.registerHelper('escape', (str) => {
return handlebars.escapeExpression(str);
});
// In template
<p>Message: {{escape message}}</p>
```
**Solution 4: Test template compilation**
```typescript
// Test if template compiles
import handlebars from 'handlebars';
import fs from 'fs';
const templateSource = fs.readFileSync('templates/campaign-email.html', 'utf8');
try {
const template = handlebars.compile(templateSource);
console.log('Template compiles successfully');
} catch (error) {
console.error('Template error:', error.message);
}
```
#### Prevention
- **HTML validation** - Validate before saving
- **Linting** - Use HTML linter in editor
- **Simple templates** - Keep templates simple
- **Testing** - Test rendering before deploying
---
## Queue Issues
### Queue Stuck
**Severity:** 🟠 High
#### Symptoms
Emails queued but not sending. Queue shows jobs but no progress.
#### Solutions
**Solution 1: Check queue status**
```bash
# View queue stats
curl http://localhost:4000/api/influence/email-queue/stats \
-H "Authorization: Bearer YOUR_TOKEN"
# Shows:
# {
# "waiting": 50,
# "active": 0, # Should be > 0 if processing
# "completed": 1000,
# "failed": 5
# }
```
**Solution 2: Check worker is running**
```bash
# Worker should log processing
docker compose logs api | grep -i "email worker\|processing email"
# Should show:
# Email worker started
# Processing email job for campaign: abc-123
```
**Solution 3: Restart worker**
```bash
# Restart API (restarts worker)
docker compose restart api
# Check worker started
docker compose logs api | grep "Email worker started"
```
**Solution 4: Check Redis**
```bash
# Test Redis connection
docker compose exec redis redis-cli -a YOUR_REDIS_PASSWORD ping
# Check queue keys
docker compose exec redis redis-cli -a YOUR_REDIS_PASSWORD keys "bull:email-queue:*"
```
**Solution 5: Process stuck jobs**
```bash
# Retry failed jobs
curl -X POST http://localhost:4000/api/influence/email-queue/retry-failed \
-H "Authorization: Bearer YOUR_TOKEN"
# Clean old jobs
curl -X POST http://localhost:4000/api/influence/email-queue/clean \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"status": "completed", "grace": 86400000}' # Clean completed > 1 day
```
#### Prevention
- **Health checks** - Monitor worker health
- **Auto-restart** - Restart worker if stuck
- **Alerting** - Alert if queue backed up
- **Dead letter queue** - Move repeatedly failed jobs
---
### Jobs Failing
**Severity:** 🟠 High
#### Symptoms
High failed job count. Emails not reaching recipients.
#### Solutions
**Solution 1: View failed jobs**
```bash
# Get failed job details
curl http://localhost:4000/api/influence/email-queue/failed \
-H "Authorization: Bearer YOUR_TOKEN"
# Shows:
# [
# {
# "id": "123",
# "data": { "to": "user@example.com", "subject": "..." },
# "failedReason": "SMTP connection failed",
# "attemptsMade": 3
# }
# ]
```
**Solution 2: Check error patterns**
```bash
# Common failure reasons
docker compose logs api | grep "Email failed" | sort | uniq -c
# Example output:
# 25 Email failed: Invalid email address
# 10 Email failed: SMTP connection refused
# 3 Email failed: Recipient mailbox full
```
**Solution 3: Retry with fixes**
```bash
# Fix SMTP config if needed
# Then retry failed jobs
curl -X POST http://localhost:4000/api/influence/email-queue/retry-failed \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Solution 4: Manual intervention**
For repeatedly failing emails:
1. Check email address validity
2. Verify SMTP configuration
3. Test with different recipient
4. Check if recipient's mailbox full
#### Prevention
- **Retry logic** - Auto-retry with exponential backoff
- **Email validation** - Validate before queuing
- **Error categorization** - Permanent vs transient failures
- **Bounce handling** - Handle bounce notifications
---
## Delivery Issues
### Emails Not Arriving
**Severity:** 🔴 Critical
#### Symptoms
Emails sent successfully (no errors) but not received.
#### Common Causes
1. **Spam folder** - Filtered to spam
2. **Email delay** - Taking long to deliver
3. **Email blocking** - Recipient server blocking
4. **Wrong address** - Typo in email address
#### Solutions
**Solution 1: Check spam folder**
1. Check spam/junk folder
2. Check promotions tab (Gmail)
3. Mark as "Not Spam" to whitelist
**Solution 2: Check email logs**
```bash
# Verify email was sent
docker compose logs api | grep "Email sent"
# Should show:
# Email sent to user@example.com: Campaign Email
```
**Solution 3: Use MailHog to test**
```bash
# In .env
EMAIL_TEST_MODE=true
# Restart API
docker compose restart api
# Send test email
curl -X POST http://localhost:4000/api/test-email \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"to": "test@example.com", "subject": "Test", "text": "Test"}'
# Check MailHog
# http://localhost:8025
# If appears in MailHog, SMTP working
# If not appearing in real inbox, delivery issue
```
**Solution 4: Check email headers**
In MailHog or received email:
1. View full headers
2. Check "Received" path
3. Look for spam scores
4. Check SPF/DKIM/DMARC status
**Solution 5: Test with different address**
```bash
# Try sending to different email provider
# Gmail vs Outlook vs Yahoo
# If some work and others don't, specific provider blocking
```
#### Prevention
- **Email authentication** - SPF, DKIM, DMARC
- **Reputation management** - Maintain good sender reputation
- **Bounce handling** - Monitor bounces
- **Testing** - Regular delivery tests
---
### Marked as Spam
**Severity:** 🟠 High
#### Symptoms
Emails consistently go to spam folder.
#### Solutions
**Solution 1: Configure SPF**
Add TXT record to DNS:
```
v=spf1 include:_spf.google.com ~all
```
Or for SendGrid:
```
v=spf1 include:sendgrid.net ~all
```
**Solution 2: Configure DKIM**
1. Generate DKIM keys (via email provider)
2. Add DKIM TXT record to DNS
3. Enable DKIM signing in SMTP settings
**Solution 3: Configure DMARC**
Add TXT record to DNS:
```
v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com
```
**Solution 4: Improve email content**
- Use plain text version alongside HTML
- Avoid spam trigger words ("FREE", "CLICK HERE", "ACT NOW")
- Proper from/reply-to addresses
- Unsubscribe link
- Physical address in footer
**Solution 5: Warm up IP**
If using dedicated IP:
1. Start with low volume
2. Gradually increase over weeks
3. Monitor reputation scores
#### Prevention
- **Email authentication** - SPF, DKIM, DMARC mandatory
- **Content quality** - Professional, non-spammy content
- **Reputation monitoring** - Monitor sender scores
- **Engagement** - High engagement = good reputation
---
### Bounce Errors
**Severity:** 🟡 Medium
#### Symptoms
```
Email bounced: user@example.com
554 Recipient address rejected: User unknown
```
#### Common Causes
1. **Invalid address** - Email doesn't exist
2. **Full mailbox** - Recipient mailbox full
3. **Temporary failure** - Server temporarily unavailable
4. **Blocked sender** - Your domain/IP blocked
#### Solutions
**Solution 1: Categorize bounces**
Hard bounces (permanent):
- User unknown
- Domain doesn't exist
- Invalid address format
Soft bounces (temporary):
- Mailbox full
- Server temporarily unavailable
- Message too large
**Solution 2: Handle hard bounces**
```bash
# Remove hard bounce addresses
docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \
-c "UPDATE \"User\" SET \"emailBounced\" = true
WHERE email = 'bounced@example.com';"
# Don't send to bounced addresses
```
**Solution 3: Retry soft bounces**
```bash
# Retry soft bounces after delay
curl -X POST http://localhost:4000/api/influence/email-queue/retry-failed \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Solution 4: Validate emails before sending**
```typescript
import validator from 'validator';
const isValidEmail = validator.isEmail(email);
if (!isValidEmail) {
throw new Error('Invalid email address');
}
```
#### Prevention
- **Email validation** - Validate before saving
- **Bounce tracking** - Track bounces per address
- **Automatic removal** - Don't send to bounced addresses
- **Double opt-in** - Confirm email addresses work
---
## Listmonk Integration
### API Connection Failed
**Severity:** 🟠 High
#### Symptoms
```
Error: Failed to connect to Listmonk API
Error: ECONNREFUSED localhost:9001
```
#### Solutions
**Solution 1: Check Listmonk is running**
```bash
docker compose ps listmonk
# Should show "Up"
# If not:
docker compose up -d listmonk
```
**Solution 2: Verify API credentials**
```bash
# Check .env
cat .env | grep LISTMONK_
# Required:
LISTMONK_URL=http://listmonk:9001
LISTMONK_ADMIN_USER=admin
LISTMONK_ADMIN_PASSWORD=password
```
**Solution 3: Test API connection**
```bash
# From API container
docker compose exec api curl -u admin:password http://listmonk:9001/api/health
# Should return:
# {"data": "OK"}
```
**Solution 4: Check Docker network**
```bash
# Both on same network?
docker inspect changemaker-lite-api-1 | grep NetworkMode
docker inspect changemaker-lite-listmonk-1 | grep NetworkMode
# Should both show "changemaker-lite"
```
#### Prevention
- **Health checks** - Verify Listmonk health on API startup
- **Proper credentials** - Use API user (not web admin)
- **Network config** - Ensure same Docker network
- **Error handling** - Graceful degradation if Listmonk down
---
### Sync Errors
**Severity:** 🟡 Medium
#### Symptoms
```
Error: Failed to sync subscribers to Listmonk
Error: 400 Bad Request: Invalid email format
```
#### Solutions
**Solution 1: Check sync status**
Navigate to `/app/listmonk`:
- View sync statistics
- See last sync time
- Check error count
**Solution 2: View sync logs**
```bash
docker compose logs api | grep -i "listmonk\|sync"
# Shows:
# Syncing 150 participants to Listmonk
# Created list: Campaign Participants
# Added 145 subscribers, 5 failed
```
**Solution 3: Manual sync**
```bash
# Trigger manual sync
curl -X POST http://localhost:4000/api/listmonk/sync \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Solution 4: Check subscriber data**
```bash
# View failed subscribers
docker compose logs api | grep "Failed to add subscriber"
# Common issues:
# - Invalid email format
# - Email already exists
# - Missing required fields
```
#### Prevention
- **Data validation** - Validate before sync
- **Duplicate handling** - Handle existing subscribers
- **Error logging** - Log sync errors
- **Regular syncs** - Automated periodic syncs
---
## Performance Issues
### Slow Email Sending
**Severity:** 🟡 Medium
#### Symptoms
Sending emails takes several seconds each. Bulk sends very slow.
#### Solutions
**Solution 1: Use queue system**
```bash
# Don't send synchronously
# Queue emails instead
curl -X POST http://localhost:4000/api/influence/campaigns/CAMPAIGN_ID/send-bulk \
-H "Authorization: Bearer YOUR_TOKEN"
# Processes in background via queue
```
**Solution 2: Increase worker concurrency**
In `api/src/services/email-queue.service.ts`:
```typescript
const worker = new Worker('email-queue', processor, {
concurrency: 5, // Process 5 emails at a time (default: 1)
limiter: {
max: 50, // Max 50 emails per second
duration: 1000
}
});
```
**Solution 3: Use batch sending**
For transactional email services:
```typescript
// Some SMTP services support batch sending
// Send 100 emails in single API call instead of 100 separate calls
```
**Solution 4: Check SMTP performance**
```bash
# Test SMTP connection speed
time curl -X POST http://localhost:4000/api/test-email \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"to": "test@example.com", "subject": "Test", "text": "Test"}'
# Should complete in < 2 seconds
# If > 5 seconds, SMTP server slow
```
**Solution 5: Use email service**
For high volume, use transactional email service:
- SendGrid
- Mailgun
- Amazon SES
- Postmark
Faster and more reliable than SMTP.
#### Prevention
- **Queue system** - Never send synchronously
- **Worker concurrency** - Process multiple at once
- **Email service** - Use dedicated email service
- **Rate limiting** - Respect provider limits
---
### Queue Backlog
**Severity:** 🟡 Medium
#### Symptoms
Thousands of emails waiting in queue. Taking hours to process.
#### Solutions
**Solution 1: Increase worker count**
Start multiple API instances:
```yaml
# In docker-compose.yml
api:
deploy:
replicas: 3 # 3 API instances
```
Each instance runs its own worker.
**Solution 2: Increase concurrency**
See "Slow Email Sending" section above.
**Solution 3: Pause new emails**
```bash
# Pause queue
curl -X POST http://localhost:4000/api/influence/email-queue/pause \
-H "Authorization: Bearer YOUR_TOKEN"
# Process backlog
# Resume when caught up
curl -X POST http://localhost:4000/api/influence/email-queue/resume \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Solution 4: Clean old jobs**
```bash
# Remove completed jobs
curl -X POST http://localhost:4000/api/influence/email-queue/clean \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"status": "completed", "grace": 3600000}' # Older than 1 hour
```
#### Prevention
- **Monitor queue size** - Alert when > 1000 waiting
- **Rate limiting** - Don't queue faster than can process
- **Capacity planning** - Size workers for expected load
- **Cleanup jobs** - Regular cleanup of old jobs
---
## Useful Commands
### Testing Email
```bash
# Send test email
curl -X POST http://localhost:4000/api/test-email \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"to": "test@example.com",
"subject": "Test Email",
"text": "This is a test email",
"html": "<h1>Test Email</h1><p>This is a test email</p>"
}'
# Test with template
curl -X POST http://localhost:4000/api/test-template-email \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"to": "test@example.com",
"subject": "Test Template",
"template": "campaign-email",
"variables": {
"name": "Test User",
"campaignName": "Test Campaign"
}
}'
```
### Queue Management
```bash
# Get queue stats
curl http://localhost:4000/api/influence/email-queue/stats \
-H "Authorization: Bearer YOUR_TOKEN"
# Pause queue
curl -X POST http://localhost:4000/api/influence/email-queue/pause \
-H "Authorization: Bearer YOUR_TOKEN"
# Resume queue
curl -X POST http://localhost:4000/api/influence/email-queue/resume \
-H "Authorization: Bearer YOUR_TOKEN"
# Retry failed
curl -X POST http://localhost:4000/api/influence/email-queue/retry-failed \
-H "Authorization: Bearer YOUR_TOKEN"
# Clean completed
curl -X POST http://localhost:4000/api/influence/email-queue/clean \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"status": "completed", "grace": 86400000}'
```
### Listmonk Operations
```bash
# Test Listmonk connection
curl -u admin:password http://localhost:9001/api/health
# Get lists
curl -u admin:password http://localhost:9001/api/lists
# Sync subscribers
curl -X POST http://localhost:4000/api/listmonk/sync \
-H "Authorization: Bearer YOUR_TOKEN"
# Get sync status
curl http://localhost:4000/api/listmonk/status \
-H "Authorization: Bearer YOUR_TOKEN"
```
---
## Related Documentation
### Email Documentation
- [Email Issues](email-issues.md) - This guide
- [Email Templates Feature](../features/influence/email-templates.md) - Template management
- [Email Queue](../features/influence/email-queue.md) - Queue monitoring
### Other Troubleshooting
- [Common Errors](common-errors.md) - General errors
- [Performance Optimization](performance-optimization.md) - Email performance
### External Resources
- [Nodemailer Documentation](https://nodemailer.com/)
- [BullMQ Documentation](https://docs.bullmq.io/)
- [Listmonk Documentation](https://listmonk.app/docs/)
- [Gmail SMTP Settings](https://support.google.com/a/answer/176600)
- [SPF/DKIM/DMARC Guide](https://www.dmarcanalyzer.com/)
---
**Last Updated:** February 2026
**Version:** V2.0
**Status:** Complete