A nightly batch score recalculation means a contact who crosses the SQL threshold at 9am on a Tuesday does not trigger the sales notification until 2am Wednesday — a 17-hour delay on the highest-intent moment in the lead lifecycle. iso-25010:2011 performance-efficiency.time-behaviour captures the failure: the system's response time for a threshold-crossing event is measured in hours, not minutes. In B2B SaaS where intent signals decay rapidly, delayed scoring translates directly to missed pipeline: the contact who booked a competitor demo at 10am was already gone before the batch ran.
Low because batch-only scoring delays threshold actions by hours — a significant conversion impact but not a data integrity failure or compliance risk.
Update lead scores in the engagement event handler (webhook callback or queue job), not in a scheduled batch. Call threshold evaluation immediately after the score write.
// In the engagement event handler
async function handleEngagementEvent(event: EngagementEvent) {
const scoreChange = SCORE_EVENTS[event.type]?.points ?? 0
if (scoreChange === 0) return
const updated = await db.contact.update({
where: { id: event.contactId },
data: { leadScore: { increment: scoreChange } },
select: { id: true, leadScore: true }
})
// Evaluate thresholds in the same request context
await evaluateScoreThresholds(updated.id, updated.leadScore)
}
A background job queue (BullMQ, Inngest, etc.) is acceptable if it processes events within 1–5 minutes. A nightly cron is not. Enumerate all score-update triggers in the codebase and verify that none are batch-only.
ID: campaign-orchestration-sequencing.lead-scoring.realtime-updates
Severity: low
What to look for: Check how quickly lead scores are updated after an engagement event. Updates should happen within minutes, not hours or days. Look for: event-driven score updates (webhook handlers or job queues that update scores when events arrive), versus batch cron jobs that recalculate all scores on a daily or weekly basis. A daily batch is acceptable if near-real-time routing is not required, but a batch approach means a contact who crosses a scoring threshold at 9am may not receive the threshold action until the next batch run.
Pass criteria: Lead scores are updated within no more than 5 minutes of an engagement event, either via event-driven handlers or a frequently-running queue processor. Threshold actions fire within the same time window. Count all score-update triggers and enumerate which are event-driven vs. batch.
Fail criteria: Scores are only updated in a nightly or weekly batch job. A contact crossing a critical threshold (e.g., score over 100 → route to sales) may wait up to 24+ hours for the action to fire.
Skip (N/A) when: The project does not implement lead scoring.
Detail on fail: "Lead scores updated in a nightly cron job — threshold actions can be delayed up to 24 hours after the triggering event" or "No event-driven score updates found — only a weekly batch recalculation"
Remediation: Process score updates in event-driven job handlers:
// In the engagement event handler (webhook or ESP callback)
async function handleEngagementEvent(event: EngagementEvent) {
const scoreChange = SCORE_EVENTS[event.type]?.points ?? 0
if (scoreChange === 0) return
const updated = await db.contact.update({
where: { id: event.contactId },
data: { leadScore: { increment: scoreChange } },
select: { id: true, leadScore: true }
})
// Check thresholds immediately after update
await evaluateScoreThresholds(updated.id, updated.leadScore)
}