← 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" }
      }
    }
  }
}

graph process_nps {
  label: "Process NPS Response"

  persistence {
    enabled: true
    condition: @ts { return true }
    name: "nps_scores"
  }

  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: resend
    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
  }
}

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

Flow

Trigger → graph

Graph nodes