← Back to cookbook

Feedback Survey Processor

Processes NPS surveys, routes detractors for follow-up, and persists all scores.

aiswitchresend

Source

/**
 * Processes NPS survey responses. Calculates sentiment, routes
 * detractors for follow-up, and persists all scores.
 */

webhook nps_response {
  label: "NPS Response"
  schema: @json {
    {
      "type": "object",
      "required": ["email", "score", "feedback"],
      "properties": {
        "email": { "type": "string", "format": "email" },
        "score": { "type": "number", "minimum": 0, "maximum": 10 },
        "feedback": { "type": "string" }
      }
    }
  }
}

workflow process_nps {
  label: "Process NPS Response"

  root {
    type: code
    label: "Parse response"
    code: @ts {
      const input = context.nodes.root.input
      const category = input.score >= 9 ? "promoter" : input.score >= 7 ? "passive" : "detractor"
      return {
        email: input.email,
        score: input.score,
        feedback: input.feedback,
        category: category
      }
    }
    outputSchema: @json {
      {
        "type": "object",
        "properties": {
          "email": { "type": "string" },
          "score": { "type": "number" },
          "feedback": { "type": "string" },
          "category": { "type": "string" }
        }
      }
    }
  }

  node route {
    type: switch
    label: "Route by category"
    cases: ["detractor", "other"]
    router: @ts {
      if (context.nodes.root.output.category === "detractor") return "detractor"
      return "other"
    }
  }

  node analyze_detractor {
    type: ai
    label: "Analyze detractor feedback"
    kind: object
    model: "google/gemini-2.5-flash"
    prompt: @ts {
      return "A customer gave an NPS score of " + context.nodes.root.output.score + "/10. Analyze their feedback and suggest a recovery action.\n\nFeedback: " + context.nodes.root.output.feedback
    }
    schema: @json {
      {
        "type": "object",
        "required": ["root_cause", "suggested_action"],
        "properties": {
          "root_cause": { "type": "string" },
          "suggested_action": { "type": "string" },
          "urgency": { "type": "string", "enum": ["high", "medium", "low"] }
        }
      }
    }
  }

  node followup_email {
    type: email
    label: "Send follow-up"
    from: @ts { return "[email protected]" }
    to: @ts { return "[email protected]" }
    subject: @ts { return "We heard you - let us make it right" }
    text: @ts {
      return "We noticed your recent experience was not up to our standards. We take your feedback seriously and would love to chat about how we can improve. Reply to this email and we will get back to you within 24 hours."
    }
  }

  node log_score {
    type: code
    label: "Log score"
    code: @ts {
      return {
        email: context.nodes.root.output.email,
        score: context.nodes.root.output.score,
        category: context.nodes.root.output.category
      }
    }
  }

  flow {
    root -> route
    route -["detractor"]-> analyze_detractor
    analyze_detractor -> followup_email
    route -["other"]-> log_score
  }
}

stream nps_scores {
  label: "NPS scores"
  workflow: process_nps
  version: v1

  versions: {
    v1 {
      schema: @json {
        {
          "type": "object",
          "required": ["email", "score", "category"],
          "properties": {
            "email": { "type": "string" },
            "score": { "type": "number" },
            "category": { "type": "string" }
          }
        }
      }

      condition: @ts { return true }

      prepare: @ts {
        const logged = context.output.log_score
        if (logged) return logged
        return {
          email: context.nodes.root.output.email,
          score: context.nodes.root.output.score,
          category: context.nodes.root.output.category
        }
      }
    }
  }
}

trigger on_nps {
  webhook:nps_response -> process_nps
  enabled: true
}

Flow

Trigger to workflow

Workflow nodes