feat: implement MailGun webhook service for logging email events

Implemented a production-ready TypeScript/Express.js service to receive
and log MailGun webhook events (delivered, failed, opened, clicked, etc.).

Key features:
- Webhook endpoint (POST /webhook) with comprehensive event logging
- Full TypeScript type definitions for all MailGun event types
- Prometheus metrics integration for monitoring
- Health check endpoint (GET /ping)
- Comprehensive Jest test suite with 87.76% coverage
- Docker containerization with build scripts

Removed template/example code:
- All SQL/MSSQL dependencies and related code
- Example auth router and middleware
- PRTG metrics support (simplified to Prometheus only)
- Unused middleware (CORS, IP whitelist, request parsing/validation)
- Template documentation (kept only MailGun webhook API spec)

The service is clean, minimal, and focused solely on receiving and
logging MailGun webhook events to the console.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Knee Cola
2026-01-02 20:56:22 +01:00
parent 4371a9a20a
commit 7aeea9353d
39 changed files with 12015 additions and 14 deletions

View File

@@ -0,0 +1,176 @@
# MailGun Webhook API Specification
## Overview
This document specifies the API for receiving webhook events from MailGun. The service logs all received event data to the console for monitoring and debugging purposes.
## API Endpoints
### POST /webhook
Receives webhook events from MailGun when email events occur.
#### Request Format
- **Method**: POST
- **Content-Type**: `application/x-www-form-urlencoded` or `multipart/form-data`
- **Headers**:
- No custom headers required for initial implementation
#### Request Parameters
MailGun sends various parameters depending on the event type. Common parameters include:
**Event Identification:**
- `event` (string) - Type of event (delivered, failed, opened, clicked, bounced, complained, unsubscribed)
- `timestamp` (number) - Unix timestamp when the event occurred
- `token` (string) - Randomly generated string for message signature verification
- `signature` (string) - String with hexadecimal digits for signature verification
**Message Information:**
- `message-id` (string) - MailGun message ID
- `recipient` (string) - Email address of the recipient
- `domain` (string) - Domain from which the email was sent
- `Message-Id` (string) - SMTP Message-ID header
**Event-Specific Parameters:**
For **delivered** events:
- `message-headers` (string) - JSON string of message headers
For **failed** events:
- `severity` (string) - Severity level (temporary/permanent)
- `reason` (string) - Reason for failure
- `notification` (string) - Detailed notification message
For **opened** events:
- `city` (string) - City where email was opened
- `country` (string) - Country code
- `device-type` (string) - Device type (desktop/mobile/tablet)
- `client-os` (string) - Operating system
- `client-name` (string) - Email client name
- `ip` (string) - IP address
For **clicked** events:
- `url` (string) - URL that was clicked
- `city`, `country`, `device-type`, `client-os`, `client-name`, `ip` - Same as opened events
For **bounced** events:
- `code` (string) - SMTP error code
- `error` (string) - Detailed error message
- `notification` (string) - Bounce notification
For **complained** events:
- No additional parameters
For **unsubscribed** events:
- No additional parameters
#### Success Response
- **HTTP Status**: 200 OK
- **Content-Type**: `application/json`
- **Response Body**:
```json
{
"status": "received",
"message": "Webhook event logged successfully"
}
```
#### Error Responses
**Invalid Request (400 Bad Request)**:
- **Content-Type**: `application/json`
- **Response Body**:
```json
{
"error": "Invalid request format"
}
```
**Server Error (500 Internal Server Error)**:
- **Content-Type**: `application/json`
- **Response Body**:
```json
{
"error": "Internal server error"
}
```
## Execution Flow
1. **Receive webhook POST request** from MailGun
2. **Parse request body** (form-urlencoded or multipart data)
3. **Extract event data** from request parameters
4. **Log event data to console** with structured formatting:
- Event type
- Timestamp (both Unix and human-readable)
- Recipient
- All additional event-specific parameters
5. **Return success response** to MailGun
## Edge Cases
### Missing Event Type
- **Detection**: Check if `event` parameter is present
- **Handling**: Log warning and return 400 Bad Request
### Malformed Timestamp
- **Detection**: Check if `timestamp` can be parsed as number
- **Handling**: Log with current timestamp instead, continue processing
### Large Payload
- **Detection**: Monitor request body size
- **Handling**: Log truncated data if exceeds reasonable size
### Duplicate Events
- **Detection**: MailGun may send duplicate webhooks
- **Handling**: Log all events (no deduplication in initial implementation)
## Security Considerations
### Future Enhancements
For production deployment, consider:
- **Signature Verification**: Verify webhook authenticity using `timestamp`, `token`, and `signature`
- **IP Whitelisting**: Restrict to MailGun's IP ranges
- **Rate Limiting**: Prevent abuse
## Database Integration
- **Current Implementation**: No database operations required
- **Future Enhancement**: Store events in database for analysis
## Third-Party API Calls
- **Current Implementation**: No third-party API calls
- **Future Enhancement**: Could integrate with notification services
## Logging Format
Console output format:
```
========================================
MailGun Webhook Event Received
========================================
Event Type: delivered
Timestamp: 1234567890 (2024-01-01 12:00:00 UTC)
Recipient: user@example.com
Domain: mail.example.com
Message ID: &lt;20240101120000.1.ABC123@mail.example.com&gt;
----------------------------------------
Additional Parameters:
{
"message-headers": "[...]",
"token": "...",
"signature": "..."
}
========================================
```
## Implementation Notes
- Use Express body-parser middleware for form data parsing
- All logging should use structured logger (debug package)
- Maintain type safety with TypeScript interfaces for event data
- Follow template's error handling patterns