← All ivy-agents
IVYXSTUDIO · IVY AGENT
C

Churn Risk Alert

ivy.agent.churn-risk-alert · v1.0.0

ivyx

Takes an account's usage figures, has Jev rate the churn risk, and alerts Slack and the account manager when the account is at risk; otherwise logs the check through your API.

Steps

In the order the agent's file lists them, each with where its inputs come from and the values the file fixes. A branch or a loop is a step too.

  1. 01
    Rate the churn risk
    ivy.node.jev-score

    Asks TypeSafe Jev where a text or JSON state sits on an ordered scale of 2 to 10 described levels and returns a score from 0 to the top level with the nearest level, its description and the confidence.

    takes
    state from the agent's input, payload
    set
    instructions Rate this account's churn risk on these levels: healthy, watch, at risk. Falling logins, open tickets and a request t...
    levels ["healthy","watch","at risk"]
    secret
    jev_api_key the stored secret jev-api-key
    returns
    level
    Ivy Node
  2. 02
    Sum up the figures
    ivy.node.template-render

    Fills {{ name }} placeholders in a text template from a mapping, and reports which names were missing.

    takes
    values from the agent's input, payload
    set
    template Churn risk on {{ account }} ({{ plan }} plan): logins fell from {{ logins_prev_30d }} to {{ logins_last_30d }} in 30 ...
    on_missing empty
    returns
    summary
    Ivy Node
  3. 03
    Only when at risk

    Lets what follows run only when is.at_risk of step 1 is true.

    Branch
  4. 04
    Alert the channel
    ivy.node.http-request

    Sends one HTTP request to a URL and returns the status, headers and body, parsed as JSON when the response is JSON.

    Runs only on the then branch of step 3.

    takes
    json.text from step 2, text
    set
    method POST
    secret
    url the stored secret slack-webhook-url
    returns
    alerted
    Ivy Node
  5. 05
    Mail the account manager
    ivy.node.email-send

    Sends a plain-text or HTML email through an SMTP server in one step and returns the recipients it went to.

    Runs only on the then branch of step 3.

    takes
    body from step 2, text
    from_addr from the agent's input, username
    username from the agent's input
    to from the agent's input
    smtp_host from the agent's input
    smtp_port from the agent's input
    use_tls from the agent's input, smtp_tls
    set
    smtp_host smtp.gmail.com
    smtp_port 587
    use_tls true
    subject Churn risk: an account needs a call
    secret
    password the stored secret mail-password
    returns
    sent
    Ivy Node
  6. 06
    Log the check
    ivy.node.http-request

    Sends one HTTP request to a URL and returns the status, headers and body, parsed as JSON when the response is JSON.

    Runs only on the else branch of step 3.

    takes
    url from the agent's input, log_url
    json.account from the agent's input, payload.account
    json.level from step 1, level_text
    json.summary from step 2, text
    set
    method POST
    secret
    token the stored secret saas-api-key
    returns
    logged
    Ivy Node

Nodes it brings

Adding this agent in IVYX Studio adds these nodes with it. A node your workspace already has is kept as it is, even at another version.

What it touches

Collected from what each of its nodes declares, plus the model call when a step is a model turn. A declaration is the author's statement, and it is what policy rules select on.

message-sendNetwork

What it needs

  • A stored secret named jev-api-key. The agent's file carries the name, never the value.
  • A stored secret named mail-password. The agent's file carries the name, never the value.
  • A stored secret named saas-api-key. The agent's file carries the name, never the value.
  • A stored secret named slack-webhook-url. The agent's file carries the name, never the value.

Inputs

FieldTypeDescription
payloadrequiredobjectThe event your service posted: account, plan, logins_last_30d, logins_prev_30d, open_tickets and notes.
log_urlrequiredstringYour API's log endpoint; a POST records a check that raised no alert. The stand-in's address is used in the cases.
usernamerequiredstringThe sending address, which is the login name; its password is the stored secret mail-password.
torequiredstringThe account manager's address.
smtp_hoststringThe SMTP server; smtp.gmail.com when left out.
smtp_portintegerThe SMTP port; 587 when left out.
smtp_tlsbooleanUpgrade SMTP with STARTTLS; on when left out.

Outputs

FieldTypeDescription
levelstringhealthy, watch or at risk.
alertedbooleanTrue once Slack accepted the alert; absent when none was sent.
sentbooleanTrue once the server accepted the mail to the account manager; absent when none was sent.
loggedbooleanTrue once your API recorded a check that raised no alert; absent when it alerted.
summarystringThe one-paragraph summary of the figures.

Tests

2 of 2 test cases passed on Oct 7, 2026, in the publisher's own environment, before this version was published. The registry keeps that record; it does not run the cases again.

Requires: python:3.9, the stored secret jev-api-key, a Slack incoming webhook URL stored as the secret slack-webhook-url, your own API's key stored as the secret saas-api-key, an SMTP server (Gmail unless smtp_host says otherwise) with the sending account's password stored as the secret mail-password, for the cases, GreenMail on 127.0.0.1:3025 (mkdata-mail.py), for the cases, the stand-in API on 127.0.0.1:18910 (api-standin.py), which the secrets crm-1, gh-1, saas-1 and the stand-in's /slack/hook address satisfy

  • collapse

    Logins down from 41 to 2 with three open tickets and an export request is at risk: Slack and the manager hear.

    given
    smtp_host 127.0.0.1
    smtp_port 3025
    smtp_tls false
    payload {"account":"Acme Analytics","plan":"team","logins_last_30d":2,"logins_prev_30d":41,"open_tickets":3,"notes":"Asked ho...
    log_url http://127.0.0.1:18910/log
    username agent@ivyx.test
    to agent@ivyx.test
    expects
    level equals at risk
    alerted equals true
    sent equals true
    summary matches 41 to 2
  • growing

    Logins up from 30 to 38 with no tickets is healthy or watch: only the log hears.

    given
    smtp_host 127.0.0.1
    smtp_port 3025
    smtp_tls false
    payload {"account":"Globex","plan":"business","logins_last_30d":38,"logins_prev_30d":30,"open_tickets":0,"notes":"Added two s...
    log_url http://127.0.0.1:18910/log
    username agent@ivyx.test
    to agent@ivyx.test
    expects
    level matches ^(healthy|watch)$
    logged equals true