Skip to main content

๐Ÿ”„ 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:

  1. Cron Initialization: queue-manager/crons/domains/renew.js
  2. Service Processing: queue-manager/services/domains/renew.js
  3. 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_progress flag 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_progress flag 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 renewals
  • renewed_at: Timestamp of last renewal
  • account_id: Reference to account for billing
  • hostname: 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_at timestamp 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: true to 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):

  1. OneBalance Service monitors onebalance_queues collection
  2. Charge Calculation: Determines renewal cost
  3. Balance Check: Verifies account has sufficient balance
  4. Charge Processing: Deducts renewal cost from account
  5. Domain Update: Sets renewed_at timestamp on success
  6. 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_progress flag
  • 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_progress flag

๐Ÿ“Š 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_at fields
  • OneBalance System: Separate service processes onebalance_queues entries

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_progress flag
  • โš ๏ธ Account Balance: OneBalance service deducts renewal cost from account

Performance Considerationsโ€‹

  • 30-Second Polling: High frequency but low overhead (indexed queries)
  • Batch Flagging: Uses updateMany for 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

๐Ÿ’ฌ

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM