A production-ready, QStash-inspired HTTP message queue service built in Go with Redis. Schedule HTTP requests for immediate or delayed execution with automatic retries, exponential backoff, and comprehensive tracking.
- Features
- Architecture
- Quick Start
- API Reference
- Configuration
- Examples
- Development
- Testing
- Monitoring
- 📤 HTTP Message Scheduling - Send HTTP requests immediately or schedule for later
- ⏰ Delayed Execution - Schedule messages to be sent at specific times
- 🔄 Automatic Retries - Configurable retry logic with exponential backoff
- 📊 Complete Tracking - Full lifecycle logging for every message
- 💀 Dead Letter Queue - Failed messages after max retries for manual inspection
- 🔍 Status API - Query message status and execution history
- ⚡ Smart Scheduler - Efficient Redis-based task scheduling with minimal sleep
- 🔀 Parallel Workers - Multiple worker goroutines for concurrent processing
- 🎯 Wake-up Signals - Immediate processing for urgent tasks
- 🛡️ Graceful Shutdown - Clean service termination
- 📈 Health Monitoring - Built-in health checks and statistics
- 🧪 Comprehensive Testing - 99+ unit and integration tests
- 📝 Rich Logging - Detailed execution logs with worker/scheduler prefixes
- 🌐 REST API - Simple HTTP interface
- 🔧 Configurable - Defaults that work, customization when needed
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ HTTP Client │───▶│ HTTP Server │───▶│ Message Logger │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
▼ │
┌─────────────────┐ │
│ Task Storage │ │
│ (Redis Sorted │ │
│ Set) │ │
└─────────────────┘ │
│ │
▼ │
┌─────────────────┐ │
│ Task Scheduler │ │
│ (Heartbeat) │ │
└─────────────────┘ │
│ │
▼ │
┌─────────────────┐ │
│ Task Workers │ │
│ (3 Parallel) │ │
└─────────────────┘ │
│ │
▼ │
┌─────────────────┐ │
│ HTTP Client │ │
│ (Webhook) │◀─────────────┘
└─────────────────┘
- HTTP Server - Receives requests and schedules tasks
- Task Scheduler - Smart heartbeat that checks for ready tasks
- Task Workers - Execute HTTP requests and handle responses
- Message Logger - Tracks message lifecycle in Redis
- Task Storage - Redis sorted set for time-based scheduling
- Retry Manager - Handles backoff logic and retry scheduling
- Go 1.19 or later
- Redis 6.0 or later
# Clone the repository
git clone https://github.com/your-username/heartbeat.git
cd heartbeat
# Install dependencies
go mod tidy
# Build the service
go build -o ./bin/heartbeat .
# Start Redis (if not already running)
redis-server
# Run the service
./heartbeatThe service will start on port 8080 by default.
# Send an immediate message
curl -X POST http://localhost:8080/send \
-H "Content-Type: application/json" \
-d '{
"url": "https://httpbin.org/post",
"payload": {"message": "Hello World!"},
"method": "POST"
}'
# Response
{
"messageId": "msg_a1b2c3d4e5f6...",
"status": "created",
"executeAt": "2024-01-19T15:30:00Z",
"attempt": 1,
"maxRetries": 3
}Schedule a new HTTP message for delivery.
Request Body:
{
"url": "string (required)",
"payload": "object (optional)",
"headers": "object (optional)",
"method": "string (optional, default: POST)",
"maxRetries": "number (optional, default: 3)",
"delaySeconds": "number (optional, default: 0)"
}Response:
{
"messageId": "string",
"status": "created",
"executeAt": "ISO8601 timestamp",
"attempt": 1,
"maxRetries": "number"
}Example:
# Delayed message (5 minutes)
curl -X POST http://localhost:8080/send \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.example.com/webhook",
"payload": {"event": "user_signup", "userId": 123},
"headers": {"Authorization": "Bearer token123"},
"method": "POST",
"maxRetries": 5,
"delaySeconds": 300
}'Get detailed status and logs for a message.
Response:
{
"messageId": "string",
"url": "string",
"method": "string",
"status": "CREATED|ACTIVE|DELIVERED|FAILED|RETRY",
"createdAt": "ISO8601 timestamp",
"maxRetries": "number",
"scheduledTasks": "number",
"nextExecuteAt": "ISO8601 timestamp (if scheduled)",
"nextAttempt": "number (if scheduled)",
"logs": [
{
"timestamp": "ISO8601 timestamp",
"status": "string",
"attempt": "number",
"details": "string"
}
]
}Health check endpoint.
Response:
{
"status": "healthy|unhealthy",
"timestamp": "ISO8601 timestamp",
"scheduler_running": "boolean",
"stats": {
"scheduler_running": "boolean",
"max_sleep": "string",
"task_stats": {
"total_tasks": "number",
"ready_tasks": "number",
"future_tasks": "number",
"next_execution": "ISO8601 timestamp"
}
}
}Detailed service statistics.
Response:
{
"timestamp": "ISO8601 timestamp",
"scheduler_stats": {
"scheduler_running": "boolean",
"max_sleep": "string",
"task_stats": {
"total_tasks": "number",
"ready_tasks": "number",
"future_tasks": "number"
}
},
"dlq_count": "number",
"server_port": "number"
}| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
HTTP server port |
REDIS_URL |
localhost:6379 |
Redis connection string |
REDIS_DB |
0 |
Redis database number |
MAX_WORKERS |
3 |
Number of parallel workers |
MAX_RETRIES |
3 |
Default maximum retries |
BASE_DELAY |
1s |
Base delay for exponential backoff |
MAX_DELAY |
5m |
Maximum delay between retries |
The service uses exponential backoff with jitter:
- Base Delay: 1 second
- Multiplier: 2.0
- Max Delay: 5 minutes
- Max Retries: 3 (configurable per message)
Retry Schedule Example:
- Attempt 1: Immediate
- Attempt 2: ~1 second delay
- Attempt 3: ~2 second delay
- Attempt 4: ~4 second delay
- Failed: Move to Dead Letter Queue
# Send user signup notification
curl -X POST http://localhost:8080/send \
-H "Content-Type: application/json" \
-d '{
"url": "https://webhook.site/your-unique-url",
"payload": {
"event": "user.signup",
"user": {"id": 123, "email": "user@example.com"},
"timestamp": "2024-01-19T15:30:00Z"
},
"headers": {"X-Event-Type": "user.signup"}
}'# Send reminder email in 1 hour
curl -X POST http://localhost:8080/send \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.sendgrid.com/v3/mail/send",
"payload": {
"to": "user@example.com",
"subject": "Meeting Reminder",
"body": "Your meeting starts in 1 hour"
},
"headers": {"Authorization": "Bearer sg.token"},
"delaySeconds": 3600
}'# Payment failure notification (max retries)
curl -X POST http://localhost:8080/send \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.slack.com/api/chat.postMessage",
"payload": {
"channel": "#alerts",
"text": "Payment processing failed for order #12345"
},
"headers": {"Authorization": "Bearer xoxb-token"},
"maxRetries": 10
}'# Get message status
MESSAGE_ID="msg_a1b2c3d4e5f6..."
curl http://localhost:8080/status/$MESSAGE_ID | jq
# Monitor until delivered
while true; do
STATUS=$(curl -s http://localhost:8080/status/$MESSAGE_ID | jq -r '.status')
echo "Status: $STATUS"
[[ "$STATUS" == "DELIVERED" ]] && break
sleep 1
doneThis project includes a Makefile for common development tasks. Use the following commands:
# Build the project binary
make build
# Run all tests
make test
# Run all tests with verbose output
make test-verbose
# Run the application (after build)
make run
# Build and run in one step (dev mode)
make dev
# Lint the code (requires golangci-lint)
make lint
# Clean up build artifacts and kill any process on port 8080
make cleanheartbeat/
├── main.go # Application entry point
├── types.go # Core data structures
├── message_id.go # UUID generation
├── message_logger.go # Redis logging system
├── task_storage.go # Redis sorted set operations
├── retry_backoff.go # Retry logic and backoff
├── task_scheduler.go # Smart heartbeat scheduler
├── task_worker.go # HTTP request workers
├── http_server.go # REST API server
├── integration_test.go # End-to-end tests
├── *_test.go # Unit tests
├── go.mod # Dependencies
└── README.md # This file
# Development build
go build -o heartbeat .
# Production build with optimizations
go build -ldflags="-s -w" -o heartbeat .
# Cross-compile for Linux
GOOS=linux GOARCH=amd64 go build -o heartbeat-linux .# Run with verbose logging
go run . -v
# Run with custom port
PORT=9090 go run .
# Run with different Redis
REDIS_URL=redis://localhost:6380 go run .The service has comprehensive test coverage with 99+ tests.
# Run all tests
go test ./...
# Run with verbose output
go test -v ./...
# Run with coverage
go test -cover ./...
# Run integration tests only
go test -v integration_test.go *.go- Unit Tests: Test individual components in isolation
- Integration Tests: Test complete workflows end-to-end
- Redis Tests: Automatically skip if Redis unavailable
=== RUN TestIntegration_EndToEndImmediateTask
[HTTP] Created message msg_abc123, executing at 15:30:00
[SCHEDULER] Found 1 ready tasks
[WORKER-1] Processing task msg_abc123 (attempt 1)
[WORKER-1] Making POST request to https://httpbin.org/post
[WORKER-1] Task msg_abc123 succeeded: HTTP 200 OK
--- PASS: TestIntegration_EndToEndImmediateTask (1.11s)
# Basic health check
curl http://localhost:8080/health
# Detailed statistics
curl http://localhost:8080/statsThe service provides detailed structured logging:
🚀 Starting Heartbeat Message Queue Service
✅ Connected to Redis
🔄 Starting 3 task workers
⏰ Starting task scheduler
🌐 Starting HTTP server on port 8080
[HTTP] Created message msg_abc123, executing at 15:30:00
[SCHEDULER] Found 1 ready tasks
[WORKER-1] Processing task msg_abc123 (attempt 1)
[WORKER-1] Task msg_abc123 succeeded: HTTP 200 OK
Monitor these key metrics:
- Queue Depth: Number of pending tasks
- Processing Rate: Tasks processed per minute
- Error Rate: Failed tasks / total tasks
- Retry Rate: Tasks requiring retries
- DLQ Size: Failed messages in dead letter queue
Failed messages are stored in Redis for manual inspection:
# Check DLQ size
redis-cli LLEN dlq
# View failed messages
redis-cli LRANGE dlq 0 -1MIT License - see LICENSE file for details.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- 📧 Issues: GitHub Issues
- 📖 Documentation: This README and inline code comments
- 🧪 Examples: See the
examples/directory
Built with ❤️ in Go | QStash-inspired | Production Ready