← Back to Cookbook

Changelog Announcer

Takes a changelog entry and generates announcements for email, Slack, and social.

aihttpresend

Source

/**
 * Takes a changelog entry and generates an announcement
 * for multiple channels: email, Slack, and social media.
 */

form changelog_entry {
  label: "Changelog Entry"
  schema: @json {
    {
      "type": "object",
      "required": ["version", "changes"],
      "properties": {
        "version": { "type": "string", "title": "Version" },
        "changes": { "type": "string", "title": "What Changed" },
        "breaking": { "type": "boolean", "title": "Breaking Changes?" }
      }
    }
  }
}

secret announce_config {
  vars: [SLACK_WEBHOOK_URL]
}

graph announce_release {
  label: "Announce Release"

  root {
    type: code
    label: "Extract changelog"
    code: @ts { return context.nodes.root.input }
    outputSchema: @json {
      {
        "type": "object",
        "properties": {
          "version": { "type": "string" },
          "changes": { "type": "string" },
          "breaking": { "type": "boolean" }
        }
      }
    }
  }

  node generate_announcement {
    type: ai
    label: "Generate announcement"
    kind: object
    model: "google/gemini-2.5-flash"
    prompt: @ts {
      const entry = context.nodes.root.output
      return "Generate release announcements for version " + entry.version + ".\n\nChanges:\n" + entry.changes + "\n\nBreaking changes: " + (entry.breaking ? "yes" : "no") + "\n\nGenerate: a short Slack message (2-3 lines), an email body (1 paragraph), and a tweet (under 280 chars)."
    }
    schema: @json {
      {
        "type": "object",
        "required": ["slack_message", "email_body", "tweet"],
        "properties": {
          "slack_message": { "type": "string" },
          "email_body": { "type": "string" },
          "tweet": { "type": "string" }
        }
      }
    }
    temperature: 0.7
  }

  node post_slack {
    type: http
    label: "Post to Slack"
    method: "POST"
    url: @ts { return context.secrets.announce_config.SLACK_WEBHOOK_URL }
    body: @ts {
      return JSON.stringify({
        text: context.nodes.generate_announcement.output.slack_message
      })
    }
    secrets: {
      announce_config: [SLACK_WEBHOOK_URL]
    }
  }

  node send_email {
    type: resend
    label: "Send email announcement"
    from: @ts { return "[email protected]" }
    to: @ts { return "[email protected]" }
    subject: @ts { return "Release " + context.nodes.root.output.version }
    text: @ts { return context.nodes.generate_announcement.output.email_body }
  }

  node result {
    type: code
    label: "Compile results"
    code: @ts {
      return {
        version: context.nodes.root.output.version,
        tweet_draft: context.nodes.generate_announcement.output.tweet,
        tweet_length: context.nodes.generate_announcement.output.tweet.length,
        slack_sent: true,
        email_sent: true
      }
    }
  }

  flow {
    root -> generate_announcement
    generate_announcement -> post_slack
    generate_announcement -> send_email
    post_slack -> result
    send_email -> result
  }
}

trigger on_changelog {
  form:changelog_entry -> announce_release
  enabled: true
}

Flow

Trigger → graph

Graph nodes