Building VIP Priority Logic for Hotel Reconfirmation Systems
If you're building software for travel agencies, you've probably heard this request: "We need our important bookings to get handled first."
Seems simple enough. Add a boolean flag, sort by it, done. Ship it.
Except six months later, they're back. The VIP flag isn't solving their problem because their problem wasn't "mark bookings as important." Their problem was "ensure high-value bookings receive systematic different treatment throughout the reconfirmation workflow."
Let's break down how to build a VIP priority that actually solves the operational problem.
The Real Problem Space
Travel agencies don't process bookings uniformly. A $200 weekend stay needs confirmation, but if something goes wrong, the damage is limited. A $15,000 multi-property corporate booking? That's potentially a career-ending mistake.
According to industry research, cancellation rates exceed 30% across most markets. Manual reconfirmation processes consume 30-60 minutes per complex booking. When you're processing hundreds of bookings monthly, something will slip through.
The operational reality: agencies need bookings processed in priority order, with different rules governing timing, follow-up intensity, and escalation triggers based on business value.
Architecture Decisions
Option 1: Boolean Flag
// Simple but limiting
const booking = {
id: "BK123",
clientId: "CL456",
isVIP: true,
// ...other fields
}
This works for basic sorting but breaks down quickly. What about different VIP levels? What rules apply to each level? How do you audit which bookings qualified and why?
Option 2: Enum with Tiers
// Better for multiple levels
const VIPTier = {
NONE: 0,
STANDARD: 1,
PRIORITY: 2,
CRITICAL: 3
}
const booking = {
id: "BK123",
clientId: "CL456",
vipTier: VIPTier.CRITICAL,
// ...other fields
}
This allows tiered handling but still couples VIP status tightly to the booking record. Hard to audit rule changes over time.
Option 3: Rule-Based System (Recommended)
// Flexible, auditable, maintainable
const vipRules = [
{
id: "rule_001",
name: "Strategic Corporate Accounts",
tier: VIPTier.CRITICAL,
criteria: {
clientTypes: ["CORPORATE", "TMC"],
annualValue: { min: 100000 },
hasContract: true
},
actions: {
queuePosition: "FRONT",
reconfirmDaysOut: 14,
followUpAttempts: 5,
escalateTo: ["senior_ops", "account_manager"]
}
},
{
id: "rule_002",
name: "High-Value Wholesale Partners",
tier: VIPTier.CRITICAL,
criteria: {
clientTypes: ["WHOLESALE"],
annualValue: { min: 250000 }
},
actions: {
queuePosition: "FRONT",
reconfirmDaysOut: 10,
followUpAttempts: 4
}
},
{
id: "rule_003",
name: "Complex Multi-Property Bookings",
tier: VIPTier.PRIORITY,
criteria: {
propertyCount: { min: 3 },
totalValue: { min: 5000 }
},
actions: {
queuePosition: "HIGH",
reconfirmDaysOut: 10,
followUpAttempts: 3,
additionalChecks: ["ROOM_TYPE", "SPECIAL_REQUESTS"]
}
}
]
This approach provides:
Clear audit trail of which rules applied when
Easy modification without schema changes
Different actions per tier/rule
Testable logic separated from data
Implementation Patterns
Rule Evaluation Engine
function evaluateVIPStatus(booking, client, rules) {
const applicableRules = []
for (const rule of rules) {
if (matchesCriteria(booking, client, rule.criteria)) {
applicableRules.push(rule)
}
}
// Return highest tier if multiple rules match
return applicableRules.sort((a, b) => b.tier - a.tier)[0] || null
}
function matchesCriteria(booking, client, criteria) {
// Check client type
if (criteria.clientTypes && !criteria.clientTypes.includes(client.type)) {
return false
}
// Check annual value
if (criteria.annualValue?.min && client.annualValue < criteria.annualValue.min) {
return false
}
// Check contract status
if (criteria.hasContract !== undefined && client.hasContract !== criteria.hasContract) {
return false
}
// Check booking-specific criteria
if (criteria.propertyCount?.min && booking.properties.length < criteria.propertyCount.min) {
return false
}
if (criteria.totalValue?.min && booking.totalValue < criteria.totalValue.min) {
return false
}
return true
}
Queue Management
function prioritizeQueue(bookings) {
return bookings.sort((a, b) => {
// Primary sort: VIP tier (higher first)
if (a.vipTier !== b.vipTier) {
return b.vipTier - a.vipTier
}
// Secondary sort: Days until check-in (sooner first)
const aDays = dayUntil(a.checkInDate)
const bDays = dayUntil(b.checkInDate)
if (aDays !== bDays) {
return aDays - bDays
}
// Tertiary sort: Booking value (higher first)
return b.totalValue - a.totalValue
})
}
Reconfirmation Scheduler
function scheduleReconfirmation(booking, vipRule) {
const checkInDate = new Date(booking.checkInDate)
const daysOut = vipRule?.actions.reconfirmDaysOut || 7 // Default 7 days
const reconfirmDate = new Date(checkInDate)
reconfirmDate.setDate(reconfirmDate.getDate() - daysOut)
return {
bookingId: booking.id,
scheduledDate: reconfirmDate,
attempts: vipRule?.actions.followUpAttempts || 2,
escalateTo: vipRule?.actions.escalateTo || []
}
}
Escalation Logic
async function checkEscalation(reconfirmation) {
const daysSince = daysSinceLastAttempt(reconfirmation)
const maxDays = reconfirmation.vipRule?.actions.escalationThreshold || 3
if (daysSince >= maxDays && !reconfirmation.confirmed) {
await sendEscalation({
bookingId: reconfirmation.bookingId,
recipients: reconfirmation.vipRule?.actions.escalateTo,
reason: "NO_CONFIRMATION",
attemptsMade: reconfirmation.attemptsMade,
daysUntilCheckIn: daysUntil(reconfirmation.checkInDate)
})
// Log escalation event
await auditLog.create({
type: "VIP_ESCALATION",
bookingId: reconfirmation.bookingId,
ruleId: reconfirmation.vipRule?.id,
timestamp: new Date()
})
}
}
Database Design Considerations
Booking Table
CREATE TABLE bookings (
id UUID PRIMARY KEY,
client_id UUID NOT NULL,
check_in_date DATE NOT NULL,
total_value DECIMAL(10,2),
property_count INT,
vip_tier INT DEFAULT 0,
applied_rule_id VARCHAR(50),
created_at TIMESTAMP,
FOREIGN KEY (client_id) REFERENCES clients(id)
)
CREATE INDEX idx_vip_checkin ON bookings(vip_tier DESC, check_in_date ASC)
VIP Rule Application Log
CREATE TABLE vip_rule_applications (
id UUID PRIMARY KEY,
booking_id UUID NOT NULL,
rule_id VARCHAR(50) NOT NULL,
tier INT NOT NULL,
applied_at TIMESTAMP NOT NULL,
criteria_snapshot JSONB,
FOREIGN KEY (booking_id) REFERENCES bookings(id)
)
This audit trail lets you answer questions like "Why was this booking VIP?" and "When did the rule change?"
API Design
Evaluating VIP Status
POST /api/bookings/:id/evaluate-vip
{
"forceRecalculation": true
}
Response:
{
"bookingId": "BK123",
"vipTier": 3,
"appliedRule": {
"id": "rule_001",
"name": "Strategic Corporate Accounts",
"tier": 3
},
"actions": {
"queuePosition": "FRONT",
"reconfirmDaysOut": 14,
"followUpAttempts": 5,
"escalateTo": ["senior_ops", "account_manager"]
}
}
Getting Priority Queue
GET /api/reconfirmations/queue?tier=CRITICAL&limit=50
Response:
{
"items": [
{
"bookingId": "BK123",
"clientName": "Acme Corp",
"checkInDate": "2025-02-15",
"vipTier": 3,
"daysUntilCheckIn": 12,
"reconfirmStatus": "PENDING"
}
],
"total": 147
}
Testing Strategies
Rule Evaluation Tests
describe('VIP Rule Evaluation', () => {
it('applies corporate account rule for qualifying clients', () => {
const booking = createBooking({ totalValue: 10000 })
const client = createClient({
type: 'CORPORATE',
annualValue: 150000,
hasContract: true
})
const result = evaluateVIPStatus(booking, client, vipRules)
expect(result.tier).toBe(VIPTier.CRITICAL)
expect(result.actions.reconfirmDaysOut).toBe(14)
})
it('applies highest tier when multiple rules match', () => {
const booking = createBooking({
totalValue: 6000,
properties: ['P1', 'P2', 'P3', 'P4']
})
const client = createClient({
type: 'WHOLESALE',
annualValue: 300000
})
const result = evaluateVIPStatus(booking, client, vipRules)
// Should match both wholesale and complex booking rules
// Should return CRITICAL (wholesale) over PRIORITY (complex)
expect(result.tier).toBe(VIPTier.CRITICAL)
})
})
Queue Prioritisation Tests
describe('Queue Prioritization', () => {
it('sorts by VIP tier first, then check-in date', () => {
const bookings = [
createBooking({ vipTier: 2, checkInDate: '2025-02-10' }),
createBooking({ vipTier: 3, checkInDate: '2025-02-15' }),
createBooking({ vipTier: 2, checkInDate: '2025-02-05' })
]
const queue = prioritizeQueue(bookings)
expect(queue[0].vipTier).toBe(3) // Highest tier first
expect(queue[1].checkInDate).toBe('2025-02-05') // Soonest among tier 2
})
})
Performance Considerations
For agencies processing thousands of bookings, rule evaluation needs to be efficient:
Cache rule evaluation results - Recalculate only when booking/client data changes
Index appropriately - Composite index on (vip_tier, check_in_date) for queue queries
Batch rule evaluation - Process multiple bookings in a single pass through rules
Lazy escalation checks - Run escalation logic on schedule, not on every booking update
Real-World Platform Example
Zeal Connect's reconfirmation platform implements VIP priority through its SLA and prioritisation settings. Users can define rules based on suppliers, ratings, countries, or client types, with the system automatically highlighting VIP bookings and triggering escalations before check-in.
Their approach demonstrates the value of rule-based configuration that doesn't require custom code for each agency's criteria.
Key Takeaways
Building an effective VIP priority requires:
Rule-based architecture for flexibility and auditability
Clear tier system with specific actions per tier
Audit logging to answer "why was this VIP?"
Queue management that respects priority and urgency
Automated escalation to prevent oversights
Performance optimization for high-volume operations
The simple Boolean flag approach works for demos. For production systems handling real agency operations, you need the architecture to support the actual business complexity.