Back
rule-engine validation use-cases
APR 30

Rule Engines for Forms That Don't Exist Yet

When your product needs validation rules you've never encountered, your schema should be ready to learn them.

👤
Postbox Team
· · 6 min read

The best use case for a rule engine isn’t the one you’re building today. It’s the one that didn’t exist when you wrote your validation logic last month.

You’re shipping a B2B SaaS form. Email required, name required, maybe a dropdown for role. It works fine until:

  • Your sales team says “actually we need to know if they’re enterprise or SMB”
  • Marketing adds a coupon code field that should only appear during promotions
  • A regulatory update requires country for certain user segments
  • Someone submits their phone number as an email and the whole thing breaks

In the UI-first model, each of these changes means editing forms, redeploying code, updating validation logic somewhere, testing, and hoping nothing broke. With a rule engine that treats schema as data, you update the schema. The contract updates. Nothing else changes.

Rules as first-class objects

Postbox doesn’t treat validation rules as if-statements scattered across backend code. They live with the field definitions, alongside type and name. That changes how you think about form evolution.

{
  "fields": [
    {
      "name": "coupon_code",
      "type": "string",
      "rules": [
        { "op": "required" },
        {
          "op": "one_of",
          "values": ["SUMMER20", "WELCOME10", "LAUNCH"],
          "when": { "field": "promotion_active", "is": "eq", "value": true }
        }
      ]
    },
    {
      "name": "company_size",
      "type": "string",
      "rules": [
        {
          "op": "required",
          "when": { "field": "account_type", "is": "eq", "value": "enterprise" }
        },
        {
          "op": "one_of",
          "values": ["1-10", "11-50", "51-200", "201-500", "500+"],
          "when": { "field": "company_size", "is": "filled" }
        }
      ]
    },
    {
      "name": "tax_id",
      "type": "string",
      "rules": [
        {
          "op": "required",
          "when": { "field": "country", "is": "eq", "value": "US" }
        },
        {
          "op": "pattern",
          "value": "^\\d{2}-\\d{4}-\\d{6}$",
          "when": { "field": "country", "is": "eq", "value": "US" }
        }
      ]
    }
  ]
}

tax_id isn’t required for everyone. It’s only required when country is US. And the format rule only applies under that same condition. The rules know when they should apply.

Conditional logic without code branching

This is the core of what makes rule engines powerful: conditional validation lives in the schema, not your application code.

You’re building a tiered pricing form where different fields appear for different tiers. With traditional forms, you write JavaScript conditionals, backend if-statements, and a database query that only fires under certain conditions. It spreads across your stack. It’s hard to test. It breaks when one team changes assumptions without telling another.

With rule-based conditional validation:

  • The schema says “if tier is enterprise, collect legal_entity_number”
  • The endpoint enforces this automatically
  • Changing tier names or requirements means updating the schema once
  • Every client — web, mobile, backend, agent — sees the updated rules immediately

No branching. No scattered conditionals. Just declarative rules that describe your business logic as data.

Business validation, not just type checking

Form tools love to focus on type safety: “this must be an email,” “this must be a number,” “this can’t be empty.” That’s table stakes.

The interesting constraints are the ones that only make sense in context:

  • “Seniority level must match years of experience” — Both fields need to be present and consistent before submission
  • “Discount can’t exceed 100 minus their loyalty tier bonus” — Validation depends on another field’s value
  • “Delivery date must be at least 7 days out OR after the holiday blackout period” — The constraint shifts based on time
  • “This field is only valid for customers in specific industries” — External data influences what’s acceptable

Rule engines handle these naturally. Each rule knows what operator to apply, what threshold to use, and when it applies (via the when clause). That’s business logic made executable as data. You don’t write code that checks whether seniority matches experience years. You define two fields with a dependency rule between them, and the validation engine figures out what to do.

The agent advantage: discovery before submission

When agents interact with your forms, they shouldn’t guess what rules exist. They shouldn’t learn from 422 errors. They shouldn’t hallucinate a format that happens to be wrong.

With Postbox’s self-documenting endpoint, an agent discovers the complete schema first:

GET https://usepostbox.com/api/{segment}/f/feedback
Accept: application/json

Response includes every field with every rule and every conditional dependency:

{
  "fields": [
    {
      "name": "issue_type",
      "type": "string",
      "rules": [
        { "op": "one_of", "values": ["billing", "technical", "account"] }
      ]
    },
    {
      "name": "refund_amount",
      "type": "number",
      "rules": [
        {
          "op": "required",
          "when": { "field": "issue_type", "is": "eq", "value": "billing" }
        },
        {
          "op": "max",
          "value": 1000,
          "when": { "field": "customer_tier", "is": "eq", "value": "standard" }
        }
      ]
    }
  ]
}

The agent now knows: issue_type can only be billing, technical, or account. If the issue is billing and the customer tier is standard, refund_amount is required and capped at 1000. It can construct a valid payload before ever submitting.

No trial-and-error. No brittle scraping. Just explicit contracts that agents can reason about.

Error messages that make sense

With type checking alone, you get generic errors: “email is invalid.” With business rules, you can give context.

{
  "name": "age",
  "type": "number",
  "rules": [
    {
      "op": "min",
      "value": 18,
      "error_message": "You must be at least 18 to purchase this product."
    }
  ]
}

The validation engine doesn’t just tell you something failed. It tells you why, based on the context it has. And if you want to customize error messages for specific rules, you do so declaratively — no code changes.

Versioning that respects backward compatibility

Your rule schema evolves. Six months ago you had five fields. Today you need seven. Old integrations are still submitting the old shape.

Schema versioning handles this automatically. When you update a form’s schema, Postbox generates a new endpoint URL:

  • POST /api/{segment-v1}/f/feedback — validated against the v1 schema (5 fields)
  • POST /api/{segment-v2}/f/feedback — validated against the v2 schema (7 fields)

Old endpoints keep working. Old integrations don’t break. Clients on the new URL get the latest validation rules immediately. Everyone wins, because old URLs reference old schemas by design.

Rule operators that cover real cases

Operator Use case
required Field must be present and non-empty
one_of Value must be in a list (dropdowns, status choices)
not_one_of Value must NOT be in a list (block specific inputs)
min / max Numeric bounds (ratings, quantities, amounts)
min_length / max_length String constraints (passwords, codes)
pattern Regex validation (email, tax IDs, phone formats)
honeypot Mark field as spam trap (invisible to humans)
after / before Date boundaries (cutoff dates, deadlines)

On their own, these are useful. Combined with conditional when clauses, they’re the building blocks for sophisticated business logic — no code required.

Start simple, evolve as needed

Rule engines can encourage over-specification. You might be tempted to define every possible scenario upfront. Don’t.

Most forms only need basic type checking and a few one_of constraints. Add conditional rules when your business actually needs them. Add pattern matching for formats that matter. Update your schema as requirements emerge.

The rule engine is there when you need it. The real point is that when you do need it, you don’t have to write code. You update a schema. The endpoint is the same URL. Everything just works.


See how Postbox’s rule engine works in the API reference, or read about our broader approach to schema versioning and self-documenting endpoints in the blog.

Have thoughts?
Or connect for more dispatches.