StratIQX Email System Troubleshooting & Integration Guide
Overview
This document provides a comprehensive guide for troubleshooting and working with the StratIQX email delivery system, including the critical X-Service authentication pattern for service-to-service communication.
Problem Summary: Email Delivery Failure
Initial Issue
Payment webhooks were returning HTTP 200 (success) but confirmation emails were not being sent to customers after successful payments through the onboarding flow.
Root Cause Analysis
The issue was multi-layered and required systematic investigation:
- Missing API Endpoint: Payment service expected
/auth/user/{userId}but auth service only had/admin/users/{userId} - Weak Service Authentication: Fallback tokens masked configuration issues
- Incomplete Email Implementation: Only one of two required emails was being sent
Architecture Overview
Service Communication Flow
Onboarding Flow → Payment Service → Auth Service → Email Service
↓ ↓ ↓ ↓
User Data → Webhook → User Lookup → Email Send
Created Processing (X-Service) (Resend API)Key Services
- Payment Service (
stratiqx-payment): Handles Stripe webhooks and payment processing - Auth Service (
stratiqx-identity-server): Manages user authentication and data - Email Service (Resend API): Delivers confirmation emails
X-Service Authentication Pattern
Purpose
The X-Service header provides service-to-service authentication that bypasses user permission checks while maintaining security.
Implementation
Payment Service (Client)
const authResponse = await fetch(authUrl, {
headers: {
'Authorization': `Bearer ${env.AUTH_ADMIN_TOKEN}`,
'Content-Type': 'application/json',
'X-Service': 'stratiqx-payment' // Critical for service auth
}
});Auth Service (Server)
async function handleGetUserForService(request, env) {
const authHeader = request.headers.get('Authorization');
const serviceHeader = request.headers.get('X-Service');
// Strict validation - no fallbacks
if (!authHeader) {
return Response.json({ error: 'Authorization header required' }, { status: 401 });
}
if (!serviceHeader) {
return Response.json({ error: 'X-Service header required for service calls' }, { status: 401 });
}
if (!authHeader.includes('admin-token')) {
return Response.json({ error: 'Invalid service token' }, { status: 401 });
}
// Service authenticated - proceed with user lookup
console.log(`✅ Service ${serviceHeader} requesting user ${userId}`);
}CORS Considerations
- Webhook Workers: Can send
X-Serviceheader (no browser restrictions) - Browser Clients: Cannot send
X-Serviceheader due to CORS preflight restrictions - Test Harnesses: Must handle authentication differently in browser environments
Troubleshooting Methodology
1. Comprehensive Logging Strategy
Step-by-Step Webhook Tracking
const progress = {
bodyRead: false,
signatureValidated: false,
eventParsed: false,
handlerCalled: false,
emailSent: false
};
console.log('✅ Step 1: Body read successfully');
console.log('✅ Step 2: Signature header present');
// ... continue through all stepsService Call Logging
console.log(`🔍 Calling auth service: ${authUrl}`);
console.log(`🔥 Auth service response: ${response.status} ${response.statusText}`);
if (response.ok) {
console.log('Auth service user data:', userData);
}2. Test Harness Development
Real Stripe Signature Generation
const generateWebhookSignature = async (payload, secret) => {
const timestamp = Math.floor(Date.now() / 1000);
const signedPayload = `${timestamp}.${payload}`;
const encoder = new TextEncoder();
const keyData = encoder.encode(secret);
const payloadData = encoder.encode(signedPayload);
const cryptoKey = await crypto.subtle.importKey(
'raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
);
const signature = await crypto.subtle.sign('HMAC', cryptoKey, payloadData);
const signatureHex = Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0')).join('');
return `t=${timestamp},v1=${signatureHex}`;
};Service Authentication Testing
// Test auth service lookup with proper error handling
const testAuthService = async (userId) => {
try {
const response = await fetch(`https://auth.stratiqx.ai/auth/user/${userId}`, {
headers: {
'Authorization': 'Bearer admin-token',
'Content-Type': 'application/json'
// Note: X-Service header omitted due to browser CORS
}
});
if (response.status === 401) {
console.log('Expected: Missing X-Service header (browser limitation)');
}
} catch (error) {
console.error('Auth service test failed:', error);
}
};3. Environment Configuration Validation
Required Secrets
# Payment Service
wrangler secret put AUTH_ADMIN_TOKEN --env production
wrangler secret put STRIPE_WEBHOOK_SECRET --env production
wrangler secret put STRIPE_SECRET_KEY --env production
# Auth Service (if needed)
wrangler secret put JWT_SECRET --env productionEnvironment Variables
# wrangler.toml
[env.production.vars]
AUTH_BASE_URL = "https://auth.stratiqx.ai"
ENVIRONMENT = "production"Email System Implementation
Dual Email Strategy
1. Service Activation Email
- Subject: "Welcome to StratIQX Intelligence Services"
- Purpose: Welcome and service activation confirmation
- Content: Service details, next steps, contact information
2. Payment Confirmation Email
- Subject: "Payment Confirmed - Your Intelligence Service is Active"
- Purpose: Payment receipt and transaction details
- Content: Payment amount, transaction ID, invoice details
Implementation Pattern
// Import both email functions
import { sendServiceActivationEmail, sendPaymentConfirmationEmail } from '../services/emailService.js';
// Send both emails in sequence
async function sendConfirmationEmails(user, service, paymentIntent, env) {
try {
// Get user data from auth service
const userData = await getUserData(user.id, env);
const emailData = {
amount: service.deposit_amount,
serviceType: service.id,
serviceName: service.name,
userEmail: userData.email,
userName: userData.fullName,
paymentIntentId: paymentIntent.id,
activatedAt: new Date().toISOString()
};
// Send both emails
console.log('🚀 Calling sendServiceActivationEmail...');
await sendServiceActivationEmail(userData.email, emailData, env);
console.log('✅ Service activation email sent successfully');
console.log('🚀 Calling sendPaymentConfirmationEmail...');
await sendPaymentConfirmationEmail(userData.email, emailData, env);
console.log('✅ Payment confirmation email sent successfully');
console.log(`✅ Both emails sent successfully to ${userData.fullName} <${userData.email}>`);
} catch (error) {
console.error('❌ Email sending failed:', error);
// Don't fail payment processing for email errors
}
}API Endpoint Implementation
Service-to-Service User Lookup
Endpoint: GET /auth/user/{userId}
// Route handler in auth service
case path.startsWith('/auth/user/') && method === 'GET':
response = await handleGetUserForService(request, env);
break;
// Implementation
async function handleGetUserForService(request, env) {
const url = new URL(request.url);
const userId = url.pathname.split('/')[3];
// Strict authentication validation
const authHeader = request.headers.get('Authorization');
const serviceHeader = request.headers.get('X-Service');
if (!authHeader) {
console.log('Service auth failed - missing Authorization header');
return Response.json({ error: 'Authorization header required' }, { status: 401 });
}
if (!serviceHeader) {
console.log('Service auth failed - missing X-Service header');
return Response.json({ error: 'X-Service header required for service calls' }, { status: 401 });
}
if (!authHeader.includes('admin-token')) {
console.log('Service auth failed - invalid token');
return Response.json({ error: 'Invalid service token' }, { status: 401 });
}
// Database lookup
await initializeDatabase(env);
const user = await env.USERS_DB.prepare(
'SELECT id, email, full_name, status FROM users WHERE id = ?'
).bind(userId).first();
if (!user) {
console.log(`User ${userId} not found in database`);
return Response.json({ error: 'User not found' }, { status: 404 });
}
// Return simplified format for payment service
return Response.json({
email: user.email,
fullName: user.full_name,
id: user.id,
status: user.status
});
}Configuration Best Practices
1. No Fallback Tokens
// ❌ Bad: Fallback tokens mask configuration issues
const token = env.AUTH_ADMIN_TOKEN || 'admin-token';
// ✅ Good: Explicit validation
if (!env.AUTH_ADMIN_TOKEN) {
throw new Error('AUTH_ADMIN_TOKEN environment variable required');
}
const token = env.AUTH_ADMIN_TOKEN;2. Strict Service Authentication
// Require both Authorization and X-Service headers
if (!authHeader || !serviceHeader) {
return Response.json({
error: 'Both Authorization and X-Service headers required'
}, { status: 401 });
}3. Comprehensive Error Messages
// Specific error messages for each failure case
if (!authHeader) {
return Response.json({ error: 'Authorization header required' }, { status: 401 });
}
if (!serviceHeader) {
return Response.json({ error: 'X-Service header required for service calls' }, { status: 401 });
}
if (!authHeader.includes('admin-token')) {
return Response.json({ error: 'Invalid service token' }, { status: 401 });
}Adding New Emails: Step-by-Step Guide
1. Create Email Function
// In src/services/emailService.js
export async function sendNewEmailType(userEmail, emailData, env) {
console.log('📬 [NEW_EMAIL] sendNewEmailType called');
if (!env.RESEND_API_KEY) {
console.error('❌ RESEND_API_KEY not configured');
return;
}
const emailContent = {
from: 'StratIQX Intelligence <intelligence@stratiqx.ai>',
to: [userEmail],
subject: 'Your Subject Here',
html: generateNewEmailTemplate(emailData)
};
const response = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(emailContent)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Resend API error: ${response.status} - ${errorText}`);
}
const result = await response.json();
console.log('✅ New email sent successfully:', result.id);
return result;
}2. Import and Call in Webhook
// Import the new function
import {
sendServiceActivationEmail,
sendPaymentConfirmationEmail,
sendNewEmailType
} from '../services/emailService.js';
// Add to email sending sequence
console.log('🚀 Calling sendNewEmailType...');
await sendNewEmailType(userData.email, emailData, env);
console.log('✅ New email sent successfully');3. Test Integration
- Deploy updated services
- Test with real payment flow
- Verify all emails are sent
- Check email delivery and formatting
Monitoring and Debugging
1. Real-Time Log Monitoring
# Payment service logs
wrangler tail stratiqx-payment-prod --format pretty
# Auth service logs
wrangler tail stratiqx-identity-server-prod --format pretty2. Key Log Patterns to Watch
✅ Step 1-7: Webhook processing completed
✅ Auth service response: 200 OK
✅ Service activation email sent successfully: [email-id]
✅ Payment confirmation email sent successfully: [email-id]
✅ Both emails sent successfully to [user]3. Common Error Patterns
❌ Auth service response: 404 Not Found -> User doesn't exist
❌ Auth service response: 401 Unauthorized -> Authentication failed
❌ Missing X-Service header -> Service auth misconfigured
❌ Resend API error: [status] -> Email delivery failedSecurity Considerations
1. Service Token Management
- Use environment-specific secrets
- Rotate tokens regularly
- Never commit tokens to code
- Use
wrangler secret putfor deployment
2. Header Validation
- Always validate both Authorization and X-Service headers
- Log authentication failures for monitoring
- Use specific error messages for debugging
3. Error Handling
- Don't expose internal errors to external callers
- Log detailed errors for internal debugging
- Fail gracefully when email sending fails
Quick Reference
Essential Commands
# Deploy services
wrangler deploy --env production
# Set secrets
wrangler secret put AUTH_ADMIN_TOKEN --env production
# Monitor logs
wrangler tail [service-name] --env production --format pretty
# List secrets
wrangler secret list --env productionCritical Headers for Service Calls
headers: {
'Authorization': `Bearer ${env.AUTH_ADMIN_TOKEN}`,
'Content-Type': 'application/json',
'X-Service': 'stratiqx-payment'
}Test Harness URLs
- Production: https://preview.stratiqx.ai/test-harness
- Development: Local deployment URLs
Conclusion
The StratIQX email system requires careful coordination between multiple services with strict authentication patterns. The X-Service header is critical for service-to-service communication and must be properly implemented and tested. When adding new emails or modifying the system, always:
- Test the complete flow from payment to email delivery
- Monitor real-time logs during testing
- Validate authentication at each service boundary
- Implement comprehensive error handling with specific error messages
- Deploy incrementally and verify each component works
This approach ensures reliable email delivery and makes future debugging much easier.