Outbox Queue
EmailEngine uses queues to process background tasks including email sending. Understanding the queue system helps you monitor delivery, troubleshoot issues, and optimize performance.
Why Queues Matter
When you submit an email, EmailEngine doesn't send it immediately. Instead, it:
- Validates your request
- Adds the message to a queue
- Returns immediately with a queue ID
- Processes the queue asynchronously
- Handles retries automatically
- Notifies you via webhooks
This approach provides:
- Reliability: Automatic retries on failures
- Scalability: Handle high-volume sending
- Monitoring: Track delivery status
- Resilience: Survive crashes and restarts
Queue Technology
EmailEngine uses BullMQ for queue management, backed by Redis. BullMQ provides:
- Persistent job storage
- Automatic retry logic
- Priority queues
- Delayed jobs (scheduled sending)
- Job progress tracking
Queue Types
EmailEngine maintains three queue types:
1. Submit Queue
Handles all email sending jobs.
- Purpose: Process outbound email
- Jobs: Individual send requests
- Lifecycle: Waiting → Active → Completed/Failed/Delayed
2. Notify Queue
Handles all webhook delivery jobs.
- Purpose: Send webhook notifications
- Jobs: Webhook HTTP requests
- Retries: Automatic retry on webhook failures
3. Documents Queue
Handles document indexing jobs.
- Purpose: Index emails for search functionality
- Jobs: Document indexing tasks
- Used when: Document Store/Elasticsearch integration is enabled
Job Lifecycle
Jobs in the submit queue move through different states:
1. Waiting
Description: Jobs ready to be processed immediately.
How jobs get here:
- New submissions without
sendAtproperty - Delayed jobs whose
sendAttime has been reached - Jobs moved from Paused when queue is unpaused
What happens: Jobs are picked up one by one and moved to Active.
# View waiting jobs
curl "https://ee.example.com/v1/outbox" \
-H "Authorization: Bearer <token>"
2. Active
Description: Jobs currently being processed.
What happens:
- EmailEngine connects to SMTP server
- Transmits the message
- Waits for SMTP response
Outcomes:
- Success → Moved to Completed
- Temporary failure → Moved to Delayed (will retry)
- Permanent failure (retries exhausted) → Moved to Failed
# View active jobs (these are currently being processed)
curl "https://ee.example.com/v1/outbox" \
-H "Authorization: Bearer <token>"
3. Completed
Description: Successfully delivered jobs.
What happens:
- SMTP server accepted the message (250 OK)
messageSentwebhook is emitted- Job stored for informational purposes
Note: By default, completed jobs are not stored. To enable storage, configure "How many completed/failed queue entries to keep" in Configuration → Service.
# View completed jobs (if enabled)
curl "https://ee.example.com/v1/outbox" \
-H "Authorization: Bearer <token>"
4. Failed
Description: Jobs that failed too many times and won't be retried.
How jobs get here:
- Retried
deliveryAttemptstimes (default: 10) - All attempts failed
What happens:
messageFailedwebhook is emitted- Job stored for debugging
Common failure reasons:
- Invalid credentials
- Network errors (persistent)
- Recipient address rejected
- Message rejected by spam filter
# View failed jobs
curl "https://ee.example.com/v1/outbox" \
-H "Authorization: Bearer <token>"
5. Delayed
Description: Jobs waiting for future processing.
How jobs get here:
- New submissions with
sendAtproperty (scheduled sending) - Failed jobs that will be retried (retry delay calculated)
What happens:
- Job waits until the delay time
- Then moved to Waiting
- If failure:
messageDeliveryErrorwebhook is emitted
Retry schedule (exponential backoff with 5 second base delay):
- Attempt 1: Immediate
- Attempt 2: +10 seconds (2^1 x 5s)
- Attempt 3: +20 seconds (2^2 x 5s)
- Attempt 4: +40 seconds (2^3 x 5s)
- Attempt 5: +80 seconds (2^4 x 5s)
- Attempt 6: +160 seconds (2^5 x 5s)
- And so on, doubling each time
# View delayed jobs
curl "https://ee.example.com/v1/outbox" \
-H "Authorization: Bearer <token>"
6. Paused
Description: Jobs held when queue is paused.
How to pause: Use the Bull Board UI or API to pause the queue.
What happens:
- New jobs go to Paused instead of Waiting
- Active jobs finish processing
- When unpaused, jobs move to Waiting
Use cases:
- Maintenance windows
- Debugging issues
- Rate limit management
# Pause queue
curl -XPUT "https://ee.example.com/v1/settings/queue/submit" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"paused": true}'
# Resume queue
curl -XPUT "https://ee.example.com/v1/settings/queue/submit" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"paused": false}'
Monitoring the Queue
Bull Board UI
EmailEngine includes Bull Board, a web UI for BullMQ queues.
Access: Navigate to Tools → Bull Board in EmailEngine UI, or go directly to /admin/bull-board.
Features:
- View job counts by state
- Inspect individual jobs
- Retry failed jobs
- Delete jobs
- Pause/resume queues
- View job logs
API Access
Query queue status via the outbox API:
# Get queue summary
curl "https://ee.example.com/v1/outbox" \
-H "Authorization: Bearer <token>"
Response:
{
"account": "example",
"queued": 5,
"states": {
"waiting": 3,
"active": 1,
"delayed": 1,
"completed": 0,
"failed": 0
}
}
List Jobs by State
# List waiting jobs
curl "https://ee.example.com/v1/outbox?pageSize=10" \
-H "Authorization: Bearer <token>"
Response:
{
"jobs": [
{
"id": "abc123",
"queueId": "4646ac53857fd2b2",
"messageId": "<message-id@example.com>",
"state": "waiting",
"to": ["recipient@example.com"],
"subject": "Test message",
"created": "2025-05-14T10:00:00.000Z"
}
],
"total": 3,
"page": 0,
"pages": 1
}
Get Job Details
# Get specific job
curl "https://ee.example.com/v1/outbox/abc123" \
-H "Authorization: Bearer <token>"
Response:
{
"id": "abc123",
"queueId": "4646ac53857fd2b2",
"messageId": "<message-id@example.com>",
"state": "delayed",
"attemptsMade": 2,
"attempts": 10,
"nextAttempt": "2025-05-14T10:15:00.000Z",
"lastError": "Connection timeout",
"envelope": {
"from": "sender@example.com",
"to": ["recipient@example.com"]
},
"created": "2025-05-14T10:00:00.000Z",
"updated": "2025-05-14T10:05:00.000Z"
}
Managing Queue Jobs
Delete a Job
Use the delete outbox entry API:
# Delete job from queue
curl -XDELETE "https://ee.example.com/v1/outbox/abc123" \
-H "Authorization: Bearer <token>"
Useful for:
- Removing stuck jobs
- Canceling scheduled sends
- Clearing failed jobs
To retry a failed job, you need to delete it from the queue and resubmit the message using the submit API. The outbox API does not support automatic retry of individual jobs.
Configuration
Delivery Attempts
Configure maximum retry attempts via the web UI or API:
Via Web UI:
- Navigate to Configuration → Service
- Set "Delivery Attempts" (default: 10)
Via API when submitting a message:
{
"to": [{"address": "recipient@example.com"}],
"subject": "Test",
"text": "Hello",
"deliveryAttempts": 5
}
Default: 10 attempts
Keep Completed/Failed Jobs
By default, completed and failed jobs are removed to save Redis memory.
Enable storage:
- Navigate to Configuration → Service
- Set "Job History Limit" to the number of completed/failed jobs to keep
- Example: Set to 100 to keep last 100 completed and 100 failed jobs
Note: This only applies to new jobs, not existing ones.
SMTP Timeout
The SMTP socket timeout is set to 2 minutes (120 seconds). This is the maximum time allowed for SMTP operations before timing out.
Webhook Events
The queue system triggers webhooks at key points:
messageSent
Emitted when job moves to Completed:
{
"event": "messageSent",
"account": "example",
"date": "2025-05-14T10:32:39.499Z",
"data": {
"messageId": "<message-id@example.com>",
"queueId": "4646ac53857fd2b2",
"response": "250 2.0.0 Ok: queued as 5755482356",
"envelope": {
"from": "sender@example.com",
"to": ["recipient@example.com"]
}
}
}
messageDeliveryError
Emitted when job moves to Delayed after failure:
{
"event": "messageDeliveryError",
"account": "example",
"date": "2025-05-14T10:05:35.832Z",
"data": {
"queueId": "4646ac53857fd2b2",
"messageId": "<message-id@example.com>",
"error": "Connection timeout",
"errorCode": "ETIMEDOUT",
"smtpResponseCode": null,
"job": {
"attemptsMade": 2,
"attempts": 10,
"nextAttempt": "2025-05-14T10:10:35.465Z"
},
"envelope": {
"from": "sender@example.com",
"to": ["recipient@example.com"]
}
}
}
messageFailed
Emitted when job moves to Failed:
{
"event": "messageFailed",
"account": "example",
"date": "2025-05-14T11:58:50.181Z",
"data": {
"messageId": "<message-id@example.com>",
"queueId": "4646ac53857fd2b2",
"error": "Error: Invalid login: 535 5.7.8 Error: authentication failed",
"envelope": {
"from": "sender@example.com",
"to": ["recipient@example.com"]
}
}
}