The Stellar Portfolio Rebalancer includes a comprehensive notification system that alerts users about important portfolio events via email and webhooks.
- Email Notifications: Send alerts via SMTP (Gmail, SendGrid, Mailgun, AWS SES, etc.)
- Webhook Notifications: POST JSON payloads to custom endpoints
- Event Filtering: Subscribe to specific event types
- User Preferences: Per-user notification configuration
- Configurable backoff: Provider-specific retry timing for email and webhooks (max attempts, initial delay, exponential multiplier, cap)
- Delivery logs: Each attempt records status (
sent,retried,failed,skipped) with optionalattempt_numberandbackoff_delay_ms - Non-blocking: Notification failures don't affect core operations
Retry behavior is loaded at startup from environment variables (validated in startupConfig) and applied in notificationService via deliverWithBackoff.
| Variable | Provider | Default | Description |
|---|---|---|---|
WEBHOOK_TIMEOUT |
Webhook | 5000 |
HTTP request timeout (ms) |
WEBHOOK_RETRY_COUNT |
Webhook | 1 |
Retries after the first failure (total attempts = 1 + WEBHOOK_RETRY_COUNT) |
WEBHOOK_RETRY_DELAY |
Webhook | 1000 |
Initial backoff before the first webhook retry (ms) |
WEBHOOK_MAX_BACKOFF_MS |
Webhook | 60000 |
Maximum delay between webhook retries (ms) |
WEBHOOK_BACKOFF_MULTIPLIER |
Webhook | 2 |
Exponential multiplier per retry |
EMAIL_MAX_ATTEMPTS |
3 |
Total SMTP send attempts (including the first try) | |
EMAIL_INITIAL_BACKOFF_MS |
1000 |
Initial backoff before the first email retry (ms) | |
EMAIL_MAX_BACKOFF_MS |
30000 |
Maximum delay between email retries (ms) | |
EMAIL_BACKOFF_MULTIPLIER |
2 |
Exponential multiplier per retry |
On each failed attempt (before the final failure), the service logs a retried row with the scheduled backoff delay. After all attempts are exhausted, a failed row is written and an error is logged with maxAttempts and the last error message.
Triggered when a portfolio is rebalanced (manual or automatic).
When triggered:
- Manual rebalance executed via API
- Automatic rebalance executed by auto-rebalancer service
Payload data:
portfolioId: Portfolio identifiertrades: Number of trades executedgasUsed: Gas consumed (e.g., "0.0234 XLM")trigger: "manual" or "automatic"
Triggered when circuit breakers activate due to market conditions.
When triggered:
- High volatility detected
- Extreme price movements
- Market instability
Payload data:
asset: Asset that triggered the breakerpriceChange: Percentage changecooldownMinutes: Cooldown period
Triggered when significant price movements are detected.
When triggered:
- Asset price changes exceed threshold (typically >10%)
Payload data:
asset: Asset symbolpriceChange: Percentage changecurrentPrice: Current price in USDdirection: "increased" or "decreased"
Triggered when portfolio risk level changes.
When triggered:
- Risk level increases or decreases
- Concentration risk changes
- Volatility risk changes
Payload data:
portfolioId: Portfolio identifieroldLevel: Previous risk levelnewLevel: Current risk levelseverity: "increased" or "decreased"
All webhook notifications are sent as HTTP POST requests with the following JSON structure:
{
"event": "rebalance",
"title": "Portfolio Rebalanced",
"message": "Your portfolio has been automatically rebalanced. 3 trades executed with 0.0234 XLM gas used.",
"data": {
"portfolioId": "portfolio-123",
"trades": 3,
"gasUsed": "0.0234 XLM",
"trigger": "automatic"
},
"timestamp": "2024-02-20T10:30:00.000Z",
"userId": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}Content-Type: application/json
User-Agent: StellarPortfolioRebalancer/1.0
Your webhook endpoint should:
- Respond with HTTP 2xx status code for success
- Respond within 5 seconds (timeout)
- Handle retries gracefully (1 retry after 1 second delay)
app.post('/webhook', express.json(), (req, res) => {
const { event, title, message, data, timestamp, userId } = req.body
console.log(`Received ${event} notification for user ${userId}`)
console.log(`Message: ${message}`)
console.log(`Data:`, data)
// Process notification
// ... your logic here ...
res.status(200).json({ received: true })
})@app.route('/webhook', methods=['POST'])
def webhook():
payload = request.json
event = payload.get('event')
message = payload.get('message')
data = payload.get('data')
print(f"Received {event} notification")
print(f"Message: {message}")
# Process notification
# ... your logic here ...
return jsonify({'received': True}), 200-
Enable 2-Factor Authentication
- Go to Google Account settings
- Security → 2-Step Verification → Turn on
-
Generate App Password
- Go to: https://myaccount.google.com/apppasswords
- Select "Mail" and your device
- Copy the generated 16-character password
-
Configure Environment Variables
SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=your-email@gmail.com SMTP_PASS=your-16-char-app-password SMTP_FROM=your-email@gmail.com
-
Create SendGrid Account
- Sign up at https://sendgrid.com
-
Generate API Key
- Settings → API Keys → Create API Key
- Select "Full Access" or "Mail Send" permissions
-
Configure Environment Variables
SMTP_HOST=smtp.sendgrid.net SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=apikey SMTP_PASS=your-sendgrid-api-key SMTP_FROM=verified-sender@yourdomain.com
-
Create Mailgun Account
- Sign up at https://mailgun.com
-
Get SMTP Credentials
- Sending → Domain Settings → SMTP Credentials
-
Configure Environment Variables
SMTP_HOST=smtp.mailgun.org SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=postmaster@your-domain.mailgun.org SMTP_PASS=your-mailgun-password SMTP_FROM=noreply@your-domain.com
-
Verify Email/Domain
- AWS Console → SES → Verified Identities
-
Create SMTP Credentials
- SES → SMTP Settings → Create SMTP Credentials
-
Configure Environment Variables
SMTP_HOST=email-smtp.us-east-1.amazonaws.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=your-smtp-username SMTP_PASS=your-smtp-password SMTP_FROM=verified@yourdomain.com
Emails are sent in both plain text and HTML formats:
Portfolio Rebalanced
Your portfolio has been automatically rebalanced. 3 trades executed with 0.0234 XLM gas used.
Event Type: rebalance
Time: 2024-02-20T10:30:00.000Z
---
Stellar Portfolio Rebalancer
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #3B82F6; color: white; padding: 20px; border-radius: 8px 8px 0 0; }
.content { background: #f9fafb; padding: 20px; border-radius: 0 0 8px 8px; }
.footer { margin-top: 20px; padding-top: 20px; border-top: 1px solid #e5e7eb; font-size: 12px; color: #6b7280; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>Portfolio Rebalanced</h2>
</div>
<div class="content">
<p>Your portfolio has been automatically rebalanced. 3 trades executed with 0.0234 XLM gas used.</p>
<p><strong>Event Type:</strong> rebalance</p>
<p><strong>Time:</strong> 2024-02-20T10:30:00.000Z</p>
</div>
<div class="footer">
<p>Stellar Portfolio Rebalancer</p>
</div>
</div>
</body>
</html>POST /api/notifications/subscribe
Content-Type: application/json
{
"userId": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"emailEnabled": true,
"emailAddress": "user@example.com",
"webhookEnabled": true,
"webhookUrl": "https://your-domain.com/webhook",
"events": {
"rebalance": true,
"circuitBreaker": true,
"priceMovement": true,
"riskChange": true
}
}Response:
{
"success": true,
"message": "Notification preferences saved successfully",
"timestamp": "2024-02-20T10:30:00.000Z"
}GET /api/notifications/preferences?userId=GXXXXXXX...Response:
{
"success": true,
"preferences": {
"userId": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"emailEnabled": true,
"emailAddress": "user@example.com",
"webhookEnabled": true,
"webhookUrl": "https://your-domain.com/webhook",
"events": {
"rebalance": true,
"circuitBreaker": true,
"priceMovement": true,
"riskChange": true
}
},
"timestamp": "2024-02-20T10:30:00.000Z"
}DELETE /api/notifications/unsubscribe?userId=GXXXXXXX...Response:
{
"success": true,
"message": "Successfully unsubscribed from all notifications",
"timestamp": "2024-02-20T10:30:00.000Z"
}POST /api/v1/debug/notifications/test
Content-Type: application/json
X-Public-Key: G...
X-Message: <unix_ms_timestamp>
X-Signature: <base64_signature_of_message>
{
"userId": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"eventType": "rebalance"
}Response:
{
"success": true,
"message": "Test notification sent successfully",
"sentTo": {
"email": "user@example.com",
"webhook": "https://your-domain.com/webhook"
},
"eventType": "rebalance",
"timestamp": "2024-02-20T10:30:00.000Z"
}To test all event types locally, use the backend script:
cd backend
npm run test:notifications:devThe script iterates through rebalance, circuitBreaker, priceMovement, and riskChange using safe sample payloads.
Set ENABLE_DEBUG_ROUTES=true before running these steps locally.
-
Create Test Webhook
- Go to https://webhook.site
- Copy your unique URL
-
Configure Notification Preferences
curl -X POST http://localhost:3001/api/v1/notifications/subscribe \ -H "Content-Type: application/json" \ -d '{ "userId": "YOUR_STELLAR_ADDRESS", "emailEnabled": false, "emailAddress": "", "webhookEnabled": true, "webhookUrl": "https://webhook.site/your-unique-id", "events": { "rebalance": true, "circuitBreaker": true, "priceMovement": true, "riskChange": true } }'
-
Send Test Notification
curl -X POST http://localhost:3001/api/v1/debug/notifications/test \ -H "Content-Type: application/json" \ -H "X-Public-Key: G..." \ -H "X-Message: <unix_ms_timestamp>" \ -H "X-Signature: <base64_signature_of_message>" \ -d '{ "userId": "YOUR_STELLAR_ADDRESS", "eventType": "rebalance" }'
-
Check webhook.site
- View the received payload
- Verify JSON structure
- Check headers
-
Configure SMTP in .env
SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=your-email@gmail.com SMTP_PASS=your-app-password SMTP_FROM=your-email@gmail.com
-
Subscribe with Email
curl -X POST http://localhost:3001/api/v1/notifications/subscribe
-H "Content-Type: application/json"
-d '{
"userId": "YOUR_STELLAR_ADDRESS",
"emailEnabled": true,
"emailAddress": "your-email@gmail.com",
"webhookEnabled": false,
"webhookUrl": "",
"events": {
"rebalance": true,
"circuitBreaker": true,
"priceMovement": true,
"riskChange": true
}
}'
3. **Send Test Email**
```bash
curl -X POST http://localhost:3001/api/v1/debug/notifications/test \
-H "Content-Type: application/json" \
-H "X-Public-Key: G..." \
-H "X-Message: <unix_ms_timestamp>" \
-H "X-Signature: <base64_signature_of_message>" \
-d '{
"userId": "YOUR_STELLAR_ADDRESS",
"eventType": "rebalance"
}'
- Check Your Inbox
- Verify email received
- Check spam folder if not in inbox
- Verify HTML formatting
Problem: Emails are not being delivered
Solutions:
- Check SMTP credentials in .env
- Verify SMTP_PASS is app password (not regular password for Gmail)
- Check backend logs for error messages
- Test SMTP connection with a simple script
- Verify sender email is verified (for AWS SES, SendGrid)
Problem: Webhook notifications failing
Solutions:
- Verify webhook URL is accessible from server
- Check webhook endpoint returns 2xx status code
- Ensure webhook responds within 5 seconds
- Check backend logs for specific error messages
- Test webhook with webhook.site first
Problem: No notifications received after rebalance
Solutions:
- Verify notification preferences are saved
- Check event type is enabled in preferences
- Verify userId matches wallet address
- Check backend logs for notification attempts
- Test with
/api/v1/debug/notifications/testendpoint (requires debug routes enabled and admin headers)
- Debug test surface isolation
/api/v1/debug/*routes are intended for local development and are blocked unlessENABLE_DEBUG_ROUTES=true- Keep
ENABLE_DEBUG_ROUTES=falsein production
-
SMTP Credentials
- Never commit .env files with real credentials
- Use app passwords, not regular passwords
- Rotate credentials regularly
-
Webhook URLs
- Use HTTPS in production
- Validate webhook URLs before saving
- Implement webhook signature verification (future enhancement)
-
Rate Limiting
- Notification endpoints are rate-limited
- Maximum 10 notifications per hour per user
- Prevents spam and abuse
-
Data Privacy
- Email addresses are stored securely
- Webhook URLs are validated
- User data is not shared with third parties
- SMS notifications via Twilio
- Push notifications for mobile apps
- Webhook signature verification
- Notification templates customization
- Notification history/logs
- Batch notifications
- Notification scheduling
- Multi-language support
For issues or questions:
- GitHub Issues: https://github.com/your-repo/issues
- Documentation: https://github.com/your-repo/docs
- Email: support@stellarportfolio.com