๐ Renew Domains
๐ Overviewโ
The Renew Domains job automatically queues Lightning Domains for renewal every 30 days via the OneBalance payment system. It runs every 30 seconds, identifies domains due for renewal (not renewed in last 30 days), and creates OneBalance queue entries to process domain renewal billing. This ensures domains remain active without manual intervention.
Complete Flow:
- Cron Initialization:
queue-manager/crons/domains/renew.js - Service Processing:
queue-manager/services/domains/renew.js - Queue Definition:
queue-manager/queues/domains/renew.js
Execution Pattern: Frequent polling (every 30 seconds)
Queue Name: domains_renew
Environment Flag: QM_DOMAINS_RENEW=true (in index.js)
๐ Complete Processing Flowโ
sequenceDiagram
participant CRON as Cron Schedule<br/>(every 30 sec)
participant SERVICE as Renew Service
participant LD_DB as Lightning<br/>Domains DB
participant QUEUE as Bull Queue
participant PROCESSOR as Job Processor
participant OB_QUEUE as OneBalance<br/>Queue Collection
CRON->>SERVICE: renew()
SERVICE->>SERVICE: Calculate 30 days ago:<br/>nDaysAgo = now - 30 days
SERVICE->>LD_DB: Find domains:<br/>- included โ true<br/>- renewal_in_progress โ true<br/>- renewed_at missing OR < 30 days ago
LD_DB-->>SERVICE: Domains due for renewal
alt Domains found
SERVICE->>LD_DB: Set renewal_in_progress=true<br/>(batch updateMany)
loop Each domain
SERVICE->>QUEUE: Add job: {domain}
end
loop Each queued domain
QUEUE->>PROCESSOR: Process domain renewal
PROCESSOR->>OB_QUEUE: Create OneBalance queue entry:<br/>- event: 'lighting_domain'<br/>- account_id<br/>- additional_info: {id, hostname}
OB_QUEUE-->>PROCESSOR: Queue entry saved
PROCESSOR-->>QUEUE: done()
end
end
๐ Source Filesโ
1. Cron Initializationโ
File: queue-manager/crons/domains/renew.js
Purpose: Schedule domain renewal checks every 30 seconds
Cron Pattern: */30 * * * * * (every 30 seconds)
Initialization:
const renew = require('../../services/domains/renew');
const cron = require('node-cron');
const logger = require('../../utilities/logger');
let inProgress = false;
exports.start = async () => {
try {
cron.schedule('*/30 * * * * *', async () => {
if (!inProgress) {
inProgress = true;
await renew();
inProgress = false;
}
});
} catch (err) {
logger.error({ initiator: 'QM/domains/renew', error: err });
}
};
In-Progress Lock: Prevents overlapping executions during heavy load.
Why 30 Seconds?
- Frequent checks ensure timely renewals
- Prevents domains from expiring due to missed renewal window
- Low overhead (simple database query)
2. Service Processing (THE RENEWAL DUE DETECTION)โ
File: queue-manager/services/domains/renew.js
Purpose: Find domains due for renewal and queue for processing
Key Functions:
- Calculate 30-day renewal window
- Query domains needing renewal
- Set
renewal_in_progressflag before queuing - Add domains to Bull queue
Main Processing Function:
const Queue = require('../../queues/domains/renew');
const LightningDomain = require('../../models/lightning-domain');
const logger = require('../../utilities/logger');
module.exports = async () => {
try {
const nDaysAgo = new Date();
//setting to 30 days ago
nDaysAgo.setDate(nDaysAgo.getDate() - 30);
// Handle the promise returned by LightningDomain.find
await LightningDomain.find({
included: { $ne: true }, // Not included domains
renewal_in_progress: { $ne: true }, // Not already processing
$or: [
{ renewed_at: { $exists: false } }, // Never renewed
{ renewed_at: { $lt: nDaysAgo } }, // Last renewed > 30 days ago
],
})
.then(async domains => {
// If domains are found, proceed with the queue
if (domains.length) {
const ids = domains.map(queue => queue._id);
await LightningDomain.updateMany({ _id: { $in: ids } }, { renewal_in_progress: true });
const queue = await Queue.start();
for (const domain of domains) {
queue.add(
{ domain },
{
attempts: 3,
},
);
}
}
logger.log({
initiator: 'QM/domains/renew',
message: 'Domain renewal service processed.',
});
})
.catch(err => {
// Log the error if the database query failed
logger.error({
initiator: 'QM/domains/renew',
message: 'Error fetching domains for renewal',
error: err,
});
});
} catch (err) {
logger.error({ initiator: 'QM/domains/renew', error: err });
}
};
3. Queue Processor (THE ONEBALANCE INTEGRATION)โ
File: queue-manager/queues/domains/renew.js
Purpose: Create OneBalance queue entries for domain renewals
Key Functions:
- Create OneBalance queue entry for billing system
- Handle renewal failures
- Clear
renewal_in_progressflag on final failure
Main Processor:
const QueueWrapper = require('../../common/queue-wrapper');
const LightningDomain = require('../../models/lightning-domain');
const onebalanceQueue = require('../../models/onebalance-queue');
const logger = require('../../utilities/logger');
const processCb = async (job, done) => {
try {
let { domain } = job.data;
await new onebalanceQueue({
account_id: domain.account_id,
event: 'lighting_domain',
additional_info: {
id: domain._id,
hostname: domain.hostname,
},
}).save();
done();
} catch (err) {
logger.error({
initiator: 'QM/domains/renew',
error: err,
api_error: err?.response?.data?.errors?.[0]?.message || undefined,
});
done(err);
}
};
const failedCb = async (job, err) => {
const id = job.data._id;
try {
if (job.attemptsMade >= job.opts.attempts) {
await LightningDomain.updateOne({ _id: id }, { renewal_in_progress: false });
}
} catch (updateErr) {
logger.error({
initiator: 'QM/domains/renew',
message: 'Error updating domain renewal status',
error: updateErr,
});
}
};
let queue;
exports.start = async () => {
try {
if (!queue) queue = QueueWrapper(`domains_renew`, 'global', { processCb, failedCb });
return Promise.resolve(queue)
.then(result => {
logger.log({
initiator: 'QM/queues/domains/renew',
message: 'Queue resolved:' + result,
});
return result;
})
.catch(error => {
logger.error({
initiator: 'QM/domains/renew',
error: error,
});
throw error;
});
} catch (err) {
logger.error({
initiator: 'QM/domains/renew',
error: err,
});
}
};
๐๏ธ Collections Usedโ
lightning_domainsโ
- Operations: Find (query domains due for renewal), Update (set/clear renewal flags)
- Model:
shared/models/lightning-domain.js - Usage Context: Track domain renewal status
Query Criteria (Domains Due for Renewal):
{
included: { $ne: true }, // Not included domains
renewal_in_progress: { $ne: true }, // Not already processing
$or: [
{ renewed_at: { $exists: false } }, // Never renewed
{ renewed_at: { $lt: nDaysAgo } } // Last renewed > 30 days ago
]
}
Batch Flag Update (Before Queuing):
await LightningDomain.updateMany({ _id: { $in: ids } }, { renewal_in_progress: true });
Clear Flag on Failure:
await LightningDomain.updateOne({ _id: id }, { renewal_in_progress: false });
Key Fields:
included: Boolean flag for included domains (excluded from renewal)renewal_in_progress: Boolean flag preventing duplicate renewalsrenewed_at: Timestamp of last renewalaccount_id: Reference to account for billinghostname: Domain name (e.g.,'custom.example.com')
onebalance_queuesโ
- Operations: Create (insert renewal billing entry)
- Model:
shared/models/onebalance-queue.js - Usage Context: Queue domain renewal for OneBalance billing system
Created Document:
{
account_id: domain.account_id, // Account to bill
event: 'lighting_domain', // Event type for billing
additional_info: {
id: domain._id, // Domain ObjectId
hostname: domain.hostname // Domain name
},
created_at: new Date(), // Auto-generated timestamp
status: 'pending' // Initial status
}
OneBalance Processing:
- Separate OneBalance service monitors this collection
- Processes domain renewal charges
- Updates
renewed_attimestamp on successful payment - Handles payment failures and retries
๐ง Job Configurationโ
Cron Scheduleโ
'*/30 * * * * *'; // Every 30 seconds
Why 30-Second Interval?
- Frequent checks ensure no missed renewal windows
- 30-day renewal cycle means most runs find 0 domains
- Low overhead (indexed queries complete in < 100ms)
Renewal Windowโ
const nDaysAgo = new Date();
nDaysAgo.setDate(nDaysAgo.getDate() - 30);
30-Day Renewal Cycle:
- Standard domain renewal period
- Ensures domains remain active continuously
- Prevents expiration gaps
Job Settingsโ
queue.add(
{ domain }, // Full domain document
{
attempts: 3, // Retry up to 3 times
},
);
Job Data:
domain: Complete Lightning Domain document
Queue Configurationโ
QueueWrapper(`domains_renew`, 'global', { processCb, failedCb });
Queue Name: domains_renew
Redis Scope: global (shared across queue manager instances)
๐ Processing Logic - Detailed Flowโ
1. Renewal Window Calculationโ
30-Day Lookback:
const nDaysAgo = new Date();
nDaysAgo.setDate(nDaysAgo.getDate() - 30);
Example:
- Current date: October 13, 2025
nDaysAgo: September 13, 2025- Domains renewed before September 13 are due for renewal
2. Domain Query Logicโ
Three-Condition Filter:
Condition 1: included: { $ne: true }
- Excludes "included" domains (already paid/complimentary)
- Only processes billable domains
Condition 2: renewal_in_progress: { $ne: true }
- Prevents duplicate processing
- Uses
$ne: trueto handle null/undefined (defensive querying)
Condition 3: $or: [...]
- Never Renewed:
{ renewed_at: { $exists: false } }- New domains without renewal timestamp
- Ensures first-time renewal
- Stale Renewal:
{ renewed_at: { $lt: nDaysAgo } }- Domains last renewed > 30 days ago
- Standard renewal cycle
3. Pre-Queue Flaggingโ
Batch Flag Update:
const ids = domains.map(queue => queue._id);
await LightningDomain.updateMany({ _id: { $in: ids } }, { renewal_in_progress: true });
Why Batch Update Before Queuing?
- Prevents race condition if cron runs again before jobs complete
- Atomic operation ensures flag consistency
- More efficient than individual updates
4. OneBalance Queue Entryโ
Document Structure:
{
account_id: domain.account_id, // Billing account
event: 'lighting_domain', // Renewal event type
additional_info: {
id: domain._id, // Domain identifier
hostname: domain.hostname // Domain name
}
}
Event Type: 'lighting_domain'
- Recognized by OneBalance billing system
- Triggers domain renewal charge flow
- Standard pricing applied based on domain type
5. OneBalance Processing Flowโ
Subsequent Processing (Outside Queue Manager):
- OneBalance Service monitors
onebalance_queuescollection - Charge Calculation: Determines renewal cost
- Balance Check: Verifies account has sufficient balance
- Charge Processing: Deducts renewal cost from account
- Domain Update: Sets
renewed_attimestamp on success - Notification: Sends renewal confirmation/failure notice
Note: OneBalance processing is handled by separate service (not part of this job).
6. Failure Handlingโ
Failed Callback:
const failedCb = async (job, err) => {
const id = job.data._id;
try {
if (job.attemptsMade >= job.opts.attempts) {
await LightningDomain.updateOne({ _id: id }, { renewal_in_progress: false });
}
} catch (updateErr) {
logger.error({
initiator: 'QM/domains/renew',
message: 'Error updating domain renewal status',
error: updateErr,
});
}
};
After 3 Failed Attempts:
- Clear
renewal_in_progressflag - Domain becomes eligible for retry on next service run
- Manual intervention may be required for persistent failures
๐จ Error Handlingโ
Common Error Scenariosโ
Database Connection Errorโ
Scenario: MongoDB connection lost during query
Result: Service-level error logged, cron retries in 30 seconds.
Recovery: Mongoose reconnection handles transient failures.
OneBalance Queue Save Errorโ
Scenario: Failed to create OneBalance queue entry
Result: Job fails, retries up to 3 times.
Recovery: Retry logic handles transient database issues.
Missing Account IDโ
Scenario: Domain has null/invalid account_id
Result: OneBalance queue entry created but fails downstream processing.
Mitigation: OneBalance service handles invalid account references.
Error Propagationโ
Service Layer:
- Catches and logs query errors
- Does not throw (allows cron to continue)
Processor Layer:
- Throws errors to trigger retry
- Final failure clears
renewal_in_progressflag
๐ Monitoring & Loggingโ
Success Loggingโ
Service-Level:
logger.log({
initiator: 'QM/domains/renew',
message: 'Domain renewal service processed.',
});
Queue Initialization:
logger.log({
initiator: 'QM/queues/domains/renew',
message: 'Queue resolved:' + result,
});
Error Loggingโ
Service Query Errors:
logger.error({
initiator: 'QM/domains/renew',
message: 'Error fetching domains for renewal',
error: err,
});
Processor Errors:
logger.error({
initiator: 'QM/domains/renew',
error: err,
api_error: err?.response?.data?.errors?.[0]?.message || undefined,
});
Failure Callback Errors:
logger.error({
initiator: 'QM/domains/renew',
message: 'Error updating domain renewal status',
error: updateErr,
});
Performance Metricsโ
- Service Query Time: < 100ms (indexed queries)
- Job Processing Time: 50-200ms per domain
- Typical Volume: 0-50 domains per run (most runs find 0 domains)
- 30-Day Cycle: Domains processed approximately once per 30 days
๐ Integration Pointsโ
Triggers This Jobโ
- Cron Schedule: Every 30 seconds automatically
- Manual Trigger: Via API endpoint (if QM_HOOKS=true)
Data Dependenciesโ
- Lightning Domains: Requires
account_id,hostname,renewed_atfields - OneBalance System: Separate service processes
onebalance_queuesentries
Jobs That Depend On Thisโ
- OneBalance Processing: Monitors and processes renewal queue entries
- Domain Validation: Verifies domain status before renewal
- Domain Cancellation: Checks renewal status before cancellation
โ ๏ธ Important Notesโ
Side Effectsโ
- โ ๏ธ Billing Event: Creates OneBalance queue entry triggering domain renewal charge
- โ ๏ธ Flag Management: Sets/clears
renewal_in_progressflag - โ ๏ธ Account Balance: OneBalance service deducts renewal cost from account
Performance Considerationsโ
- 30-Second Polling: High frequency but low overhead (indexed queries)
- Batch Flagging: Uses
updateManyfor efficient flag management - Sequential Queuing: Loops through domains but processing is parallel via Bull queue
- Indexed Queries: Ensure indexes on
included,renewal_in_progress,renewed_at
Business Logicโ
Why 30-Day Renewal Cycle?
- Standard domain billing period
- Aligns with monthly billing cycles
- Prevents domain expiration
Why 30-Second Polling?
- Ensures no missed renewal windows
- Low overhead (most runs find 0 domains)
- Acceptable latency for non-critical operation
Why Included Domains Excluded?
- "Included" domains are complimentary (no charge)
- Part of package or promotion
- Renewal billing not applicable
Why Not Update renewed_at in Queue Manager?
- OneBalance service handles renewal timestamp
- Ensures timestamp only set after successful payment
- Maintains separation of concerns (billing vs scheduling)
Maintenance Notesโ
- 30-Day Window: Hardcoded, requires code change to modify
- Included Flag: Managed separately (account configurations)
- OneBalance Integration: Monitor OneBalance service for processing issues
- Database Indexes: Ensure composite index on
(included, renewal_in_progress, renewed_at)
Code Quality Noteโ
Bug in Failed Callback:
const id = job.data._id; // BUG: Should be job.data.domain._id
Issue: job.data contains { domain }, not flat domain fields.
Correct Code:
const id = job.data.domain._id;
Impact: Failed callback never clears renewal_in_progress flag, causing domains to get stuck.
Workaround: Manual database cleanup or wait for next deployment fix.
๐งช Testingโ
Manual Triggerโ
# Via API (if QM_HOOKS=true)
POST http://localhost:6002/api/trigger/domains/renew
Simulate Domain Due for Renewalโ
// Create test domain
const domain = await LightningDomain.create({
hostname: 'test.example.com',
account_id: '507f1f77bcf86cd799439011',
included: false,
renewed_at: new Date(Date.now() - 35 * 24 * 60 * 60 * 1000), // 35 days ago
});
// Wait for next cron run (30 seconds) or trigger manually
// Verify OneBalance queue entry created
const queueEntry = await onebalanceQueue.findOne({
'additional_info.id': domain._id,
});
console.log('Queue entry created:', !!queueEntry);
console.log('Event type:', queueEntry.event); // 'lighting_domain'
Test Never-Renewed Domainโ
// Create domain without renewed_at
const newDomain = await LightningDomain.create({
hostname: 'new.example.com',
account_id: '507f1f77bcf86cd799439011',
included: false,
// No renewed_at field
});
// Trigger renewal
await renew();
// Verify flagged and queued
const flagged = await LightningDomain.findById(newDomain._id);
console.log('Renewal in progress:', flagged.renewal_in_progress); // true
Test Included Domain (Should Skip)โ
// Create included domain
const includedDomain = await LightningDomain.create({
hostname: 'included.example.com',
account_id: '507f1f77bcf86cd799439011',
included: true,
renewed_at: new Date(Date.now() - 35 * 24 * 60 * 60 * 1000),
});
// Trigger renewal
await renew();
// Verify NOT queued
const queueEntry = await onebalanceQueue.findOne({
'additional_info.id': includedDomain._id,
});
console.log('Queue entry created:', !!queueEntry); // false (should skip)
Monitor Renewal Cycleโ
// Find domains due for renewal
const nDaysAgo = new Date();
nDaysAgo.setDate(nDaysAgo.getDate() - 30);
const dueForRenewal = await LightningDomain.countDocuments({
included: { $ne: true },
renewal_in_progress: { $ne: true },
$or: [{ renewed_at: { $exists: false } }, { renewed_at: { $lt: nDaysAgo } }],
});
console.log(`${dueForRenewal} domains due for renewal`);
// Check domains in progress
const inProgress = await LightningDomain.countDocuments({
renewal_in_progress: true,
});
console.log(`${inProgress} domains currently renewing`);
Verify OneBalance Queue Entry Structureโ
// Find recent renewal queue entries
const recentEntries = await onebalanceQueue.find({
event: 'lighting_domain',
created_at: { $gte: new Date(Date.now() - 60000) }, // Last minute
});
console.log(`${recentEntries.length} renewal entries in last minute`);
recentEntries.forEach(entry => {
console.log({
account: entry.account_id,
domain: entry.additional_info.hostname,
domainId: entry.additional_info.id,
event: entry.event,
});
});
Test Failure Recoveryโ
// Simulate database error during OneBalance save
const originalSave = onebalanceQueue.prototype.save;
onebalanceQueue.prototype.save = async function () {
throw new Error('Simulated database error');
};
// Trigger renewal
await renew();
// Wait for retries (3 attempts)
setTimeout(async () => {
// Verify flag cleared after final failure
const domain = await LightningDomain.findOne({
renewal_in_progress: true,
});
console.log('Flag cleared after failure:', !domain); // Should be true
// Restore original save
onebalanceQueue.prototype.save = originalSave;
}, 10000); // Wait 10 seconds
Job Type: Scheduled + Queued
Execution Frequency: Every 30 seconds
Average Duration: 50-200ms per domain
Status: Active