How to Detect Angry Customers in Front and Alert a Supervisor in Slack
Build a Spojit workflow that polls your open Front conversations on a schedule, scores each one for sentiment and churn risk with an AI agent, and pings a supervisor channel in Slack the moment a thread turns negative or escalation-bound.
What This Integration Does
Support queues move fast, and a single frustrated customer can churn before anyone senior notices. This workflow watches your live Front inbox for you: every few minutes it reads the open conversations, has an agent read the latest customer message, and quietly grades how upset the customer is and how likely they are to leave. Only the conversations that cross a threshold reach a human, so your support supervisor sees a short, signal-rich Slack alert with the customer name, a snippet of what they said, and a deep link straight into the Front thread. A senior agent can step in before the conversation sours further, instead of discovering the problem in a churn report a week later.
The workflow runs on a Schedule trigger (a recurring cron, no human action needed). On each run it pulls a page of open conversations from Front, fetches the latest message body for each, and asks a Connector node in Agent mode to return a structured score via a Response Schema. A Condition node keeps only the threads that are negative or flagged for escalation, a Transform node shapes a clean alert payload, and a Slack send-message call posts it. The workflow is stateless between runs: it reports on whatever is currently open at poll time, so re-runs simply re-evaluate the current queue. To avoid repeat pings on the same thread, you add a Front tag once a conversation has been alerted (covered in the Tips).
Prerequisites
- A Front connection in Spojit (API token), added under Connections -> Add connection. See the Front connector reference.
- A Slack connection with permission to post messages, and the target supervisor channel created (for example
#support-escalations). See the Slack connector reference. - The Slack channel ID or name you want alerts posted to, and the IANA timezone for your support hours (for example
Australia/Sydney). - Enough AI credits available, since Agent mode scoring consumes credits per conversation evaluated.
Step 1: Add a Schedule trigger to poll the queue
Create a new workflow and set the Trigger node type to Schedule. Add a 5-field cron expression and an IANA timezone. To check every 10 minutes during business hours on weekdays, use:
*/10 9-18 * * 1-5
Australia/Sydney
A single Schedule trigger can hold multiple schedules if you want different cadences (for example tighter polling at peak times). The trigger output is just { scheduledAt }; the real work starts with the Front lookup in the next step.
Step 2: List open conversations from Front
Add a Connector node on the Front connector in Direct mode and choose the list-conversations tool. This is a deterministic single-tool call, so Direct mode keeps it cheap and predictable. Use the q search field to scope the queue to open conversations only, so you do not score archived or resolved threads:
q: "is:open"
Name the output variable conversations. The tool returns a list of conversation resources plus a pagination token at _pagination.next. For a busy inbox, pass that token back into page_token on a follow-up call, or simply rely on the next scheduled run to catch what a single page missed.
Step 3: Loop and fetch the latest message for each conversation
Add a Loop node in ForEach mode over the conversation list, for example {{ conversations._results }}, exposing each item as conversation. Inside the loop, add a Connector node on the Front connector in Direct mode using the get-conversation tool, mapping the id input to the current item:
id: {{ conversation.id }}
Store the result as detail. This gives you the full conversation including the subject, the contact, and the most recent customer message text that you will hand to the scoring step. Keeping list-then-detail split means the list call stays light and you only pull full bodies for the threads you actually evaluate.
Step 4: Score sentiment and churn risk with Agent mode and a Response Schema
Still inside the loop, add a Connector node in Agent mode. Agent mode lets the agent read the message and apply judgment, then return clean JSON you can branch on. Write a focused prompt and attach a Response Schema so the output is always structured. Example prompt:
Read this customer support message and rate it.
Subject: {{ detail.subject }}
Message: {{ detail.last_message_body }}
Return sentiment as one of positive, neutral, or negative.
Return churn_risk from 0 to 100.
Set escalate to true if the customer is angry, threatening to
leave, mentions a refund or cancellation, or demands a manager.
Give a one-sentence reason.
Set the Response Schema to force the shape:
{
"type": "object",
"properties": {
"sentiment": { "type": "string", "enum": ["positive", "neutral", "negative"] },
"churn_risk": { "type": "number" },
"escalate": { "type": "boolean" },
"reason": { "type": "string" }
},
"required": ["sentiment", "churn_risk", "escalate", "reason"]
}
Store the output as score. Because the schema is enforced, downstream nodes can safely read {{ score.sentiment }}, {{ score.churn_risk }}, and {{ score.escalate }} without defensive parsing.
Step 5: Isolate the threads that need a human with a Condition
Add a Condition node that only lets escalation-worthy conversations through. Combine the AI signals so you catch both explicitly angry threads and high churn risk:
{{ score.escalate }} is true
OR {{ score.sentiment }} equals "negative"
OR {{ score.churn_risk }} is greater than 70
Route the true branch onward to the alert steps. Leave the false branch empty so calm, healthy conversations are simply skipped and never reach Slack. This is what keeps the supervisor channel high signal: a quiet channel means everything is fine.
Step 6: Shape the alert payload with a Transform node
On the true branch, add a Transform node to assemble exactly what the supervisor needs in one place, including a deep link back into Front so they can jump straight in. Build an object like:
{
"customer": "{{ detail.recipient.handle }}",
"subject": "{{ detail.subject }}",
"snippet": "{{ score.reason }}",
"risk": "{{ score.churn_risk }}",
"sentiment": "{{ score.sentiment }}",
"link": "https://app.frontapp.com/open/{{ detail.id }}"
}
Store it as alert. Keeping the deep link and the snippet in the Transform output means the Slack step downstream stays a simple template with no logic in it.
Step 7: Post the alert to the supervisor channel in Slack
Add a Connector node on the Slack connector in Direct mode and choose the send-message tool. Set the channel to your supervisor channel and build the text from the Transform output:
channel: "#support-escalations"
text: ":rotating_light: *At-risk conversation* ({{ alert.sentiment }}, churn {{ alert.risk }})
*{{ alert.customer }}* - {{ alert.subject }}
> {{ alert.snippet }}
Open in Front: {{ alert.link }}"
That is the whole loop: every escalation-worthy Front thread becomes one tidy Slack message a senior agent can act on. If you would prefer to also email an on-call lead, add a Send Email node alongside the Slack call using Spojit's built-in mail service.
Tips
- To stop the same conversation pinging on every poll, add a Connector node on the Front connector using
add-tagafter the Slack alert (for example a tag namedspojit-alerted), then narrow the Step 2 search withq: "is:open is:unassigned"or exclude that tag so already-flagged threads drop out of the next run. - Tune the churn threshold (
70in Step 5) to your channel tolerance. Start higher to keep the channel quiet, then lower it once the team trusts the signal. - Use a Slack channel ID rather than a name in
send-messageif you rename channels often, since the ID never changes. - For very large inboxes, consider scoring only the most recently updated conversations per run and letting the schedule cadence cover the rest, to keep AI credit usage predictable.
Common Pitfalls
- Forgetting to scope Step 2 to open conversations means you score resolved and archived threads and burn AI credits for nothing. Always set the
qfilter. - Reading the AI output without a Response Schema leads to brittle branching. Keep the schema attached so
{{ score.escalate }}and{{ score.churn_risk }}are always present. - Cron is evaluated in the timezone you set on the Schedule trigger, not the viewer's timezone. A wrong IANA value can run the workflow outside support hours or skip them entirely.
- Without an alerted tag and a matching search filter, every poll re-alerts open threads. Add the tag step described in Tips before turning the schedule on.
Testing
Before enabling the schedule, set the trigger aside and run the workflow once with the Run button against your real open queue, or temporarily narrow the Step 2 search to a test conversation you control by sending yourself an obviously angry message into Front. Watch the execution log to confirm list-conversations returns rows, the Agent node produces a valid score object, the Condition routes correctly, and a single well-formed message lands in a private test Slack channel. Once the snippet, customer name, and deep link all look right, point channel at the real supervisor channel and enable the Schedule trigger.