Swirls

SWIRLS_

  • How it works
  • Pricing

56 matches

OverviewDeliver client operations10Win and grow accounts8Resolve customer issues6Create and distribute content9Run people and operations5Review money and agreements3Monitor systems and data9Compose dependable work6

56 matches

OverviewDeliver client operations10Win and grow accounts8Resolve customer issues6Create and distribute content9Run people and operations5Review money and agreements3Monitor systems and data9Compose dependable work6
← Customer SupportWebhookView on GitHub

Feedback Survey Processor

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

aiswitchemail

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 context.nodes.root.output.email }
    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

nps_responsewebhook
process_nps
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

Workflow nodes

CODE
Code Node
Parse response
SWITCH
Switch Node
Route by category
AI
AI Node
Analyze detractor feedback
EMAIL
Email Node
Send follow-up
CODE
Code Node
Log score
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.