Skip to main content

Command Palette

Search for a command to run...

Building VIP Priority Logic for Hotel Reconfirmation Systems

Published
6 min read

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.

// 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:

  1. Cache rule evaluation results - Recalculate only when booking/client data changes

  2. Index appropriately - Composite index on (vip_tier, check_in_date) for queue queries

  3. Batch rule evaluation - Process multiple bookings in a single pass through rules

  4. 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:

  1. Rule-based architecture for flexibility and auditability

  2. Clear tier system with specific actions per tier

  3. Audit logging to answer "why was this VIP?"

  4. Queue management that respects priority and urgency

  5. Automated escalation to prevent oversights

  6. 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.

More from this blog

Travel Tech

43 posts