← Back to cookbook

Daily News Digest

Fetches top Hacker News stories daily, summarizes with AI, and emails a digest.

httpmapaiemail

Source

/**
 * Fetches top stories daily, summarizes them with AI,
 * and emails a digest.
 */

schedule morning_digest {
  label: "Morning Digest"
  cron: "0 7 * * *"
  timezone: "America/New_York"
}

workflow build_digest {
  label: "Build Daily Digest"

  root {
    type: code
    label: "Start"
    code: @ts { return { timestamp: new Date().toISOString() } }
    outputSchema: @json {
      {
        "type": "object",
        "properties": {
          "timestamp": { "type": "string" }
        }
      }
    }
  }

  node fetch_tech {
    type: http
    label: "Fetch tech news"
    url: @ts { return "https://hacker-news.firebaseio.com/v0/topstories.json?limitToFirst=10&orderBy=%22$key%22" }
  }

  node fetch_stories {
    type: code
    label: "Extract story IDs"
    code: @ts {
      const ids = context.nodes.fetch_tech.output
      return { ids: Array.isArray(ids) ? ids.slice(0, 5) : [] }
    }
  }

  node fetch_details {
    type: map
    label: "Fetch each story"
    items: @ts { return context.nodes.fetch_stories.output.ids }
    maxItems: 5

    subgraph {
      root {
        type: http
        label: "Fetch story"
        inputSchema: @json { { "type": "number" } }
        url: @ts { return "https://hacker-news.firebaseio.com/v0/item/" + context.iteration.item + ".json" }
      }
    }
  }

  node collect_stories {
    type: code
    label: "Collect story titles"
    code: @ts {
      const rows = context.nodes.fetch_details.output || []
      const stories = rows
        .map(function(row) { return row.root })
        .filter(function(story) { return story && story.title })
        .map(function(story) { return { title: story.title, url: story.url || "", score: story.score || 0 } })
      return { stories: stories }
    }
  }

  node summarize {
    type: ai
    label: "Summarize headlines"
    kind: text
    model: "google/gemini-2.5-flash"
    prompt: @ts {
      const stories = context.nodes.collect_stories.output.stories
      const list = stories.map(function(s) { return "- " + s.title + " (" + s.score + " points) " + s.url }).join("\n")
      return "Write a brief daily tech news digest for " + context.nodes.root.output.timestamp + ". Open with a 2-3 sentence intro, then list each story with a one-line takeaway.\n\nToday's top Hacker News stories:\n" + list
    }
    temperature: 0.7
  }

  node send_digest {
    type: email
    label: "Send digest email"
    from: @ts { return "[email protected]" }
    to: @ts { return "[email protected]" }
    subject: @ts {
      const d = new Date(context.nodes.root.output.timestamp)
      return "Daily Tech Digest - " + d.toLocaleDateString()
    }
    text: @ts { return context.nodes.summarize.output }
  }

  flow {
    root -> fetch_tech
    fetch_tech -> fetch_stories
    fetch_stories -> fetch_details
    fetch_details -> collect_stories
    collect_stories -> summarize
    summarize -> send_digest
  }
}

trigger daily_trigger {
  schedule:morning_digest -> build_digest
  enabled: true
}

Flow

Trigger to workflow

Workflow nodes