Tutorial: Daily Report

Build a workflow that runs every morning, pulls data from your systems, aggregates it into a clean summary, and emails it to your team. No more manually checking dashboards before standup.

What you'll build

Schedule (daily 8am) → HTTP Request (get metrics) → Transformer (format report) → Send Email (to team)

Time to build: 10 minutes

Prerequisites

  • An API endpoint or data source to pull from (we'll use HTTP Request, which works with any REST API)
  • Email configured in your workspace settings

Step 1: Set up the schedule trigger

  1. Create a new workflow → + Add TriggerSchedule
  2. Configure:
FieldValue
FrequencyDaily
Time08:00
TimezoneYour team's timezone (e.g., America/New_York)
DaysMonday through Friday (skip weekends)
  1. Click Test to simulate a trigger fire; this populates the trigger output with a timestamp

Step 2: Pull data with HTTP Request

  1. Click + Add NodeFlow ControlHTTP Request
  2. Configure:
FieldValue
MethodGET
URLYour API endpoint (see examples below)
HeadersAuthorization: Bearer {{secrets.API_KEY}} (if needed)

Example: Pull from your own API

URL: https://api.yourapp.com/dashboard/daily-summary
Headers: Authorization: Bearer {{secrets.API_KEY}}

Example: Pull from Google Sheets (via API)

URL: https://sheets.googleapis.com/v4/spreadsheets/{{secrets.SHEET_ID}}/values/Dashboard!A1:F10
Headers: Authorization: Bearer {{google_sheets.access_token}}

Example: Pull from a database via TinyTables

Instead of HTTP Request, use a Find All node (Database category) to query your TinyTable directly:

FieldValue
TableYour metrics table
Filtercreated_at >= {{TODAY_START()}}
Sortcreated_at DESC
  1. Test the HTTP Request to see the response structure
  2. Save

Step 3: Transform the data into a report

  1. Click + Add NodeFlow ControlTransformer
  2. The Transformer lets you write JavaScript to reshape data. Use it to build the report body:

Transform code:

const data = inputs.http_request.body;

// Adapt these field names to match your actual API response
const report = {
  subject: `Daily Report — ${new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' })}`,
  body: `
    <h2>Daily Report</h2>
    <p>Generated at ${new Date().toLocaleTimeString()}</p>
    
    <h3>Key Metrics</h3>
    <table border="1" cellpadding="8" cellspacing="0" style="border-collapse: collapse;">
      <tr style="background: #f5f5f5;">
        <th>Metric</th>
        <th>Today</th>
        <th>Yesterday</th>
        <th>Change</th>
      </tr>
      <tr>
        <td>New signups</td>
        <td>${data.signups_today || 0}</td>
        <td>${data.signups_yesterday || 0}</td>
        <td>${data.signups_today - data.signups_yesterday > 0 ? '+' : ''}${data.signups_today - data.signups_yesterday}</td>
      </tr>
      <tr>
        <td>Active users</td>
        <td>${data.active_today || 0}</td>
        <td>${data.active_yesterday || 0}</td>
        <td>${data.active_today - data.active_yesterday > 0 ? '+' : ''}${data.active_today - data.active_yesterday}</td>
      </tr>
      <tr>
        <td>Revenue</td>
        <td>$${(data.revenue_today || 0).toLocaleString()}</td>
        <td>$${(data.revenue_yesterday || 0).toLocaleString()}</td>
        <td>${data.revenue_today - data.revenue_yesterday > 0 ? '+' : ''}$${(data.revenue_today - data.revenue_yesterday).toLocaleString()}</td>
      </tr>
    </table>

    <h3>Alerts</h3>
    <ul>
      ${(data.alerts || []).map(a => '<li>' + a + '</li>').join('')}
      ${(!data.alerts || data.alerts.length === 0) ? '<li>No alerts today</li>' : ''}
    </ul>

    <p style="color: #888; font-size: 12px;">
      This report was auto-generated by TinyCommand. 
      <a href="https://app.tinycommand.com">View dashboard</a>
    </p>
  `
};

return report;
  1. Test the transformer: verify the HTML output looks right
  2. Save

Step 4: Send the email

  1. Click + Add NodeFlow ControlSend Email
  2. Configure:
FieldValue
Toteam@yourcompany.com (or comma-separated list)
Subject{{transformer.result.subject}}
Body{{transformer.result.body}}
  1. Test: check your inbox for the formatted report
  2. Save

Step 5: Test the full workflow

  1. Click Run in the top bar to execute the entire chain
  2. Verify:
    • The HTTP Request pulls data successfully
    • The Transformer produces clean HTML
    • The email arrives with proper formatting
    • Numbers and comparisons are correct

Step 6: Publish

Click Publish. The workflow will now run automatically every weekday at 8am.

Going further

  • Multiple data sources: Add more HTTP Request nodes before the Transformer to pull from Stripe (revenue), your CRM (pipeline), your ticketing system (open bugs), etc. The Transformer can combine all of them into one report.
  • Conditional alerts: Add an If-Else after the Transformer. If a key metric drops below a threshold, send an additional Slack alert to #alerts along with the email.
  • Weekly summary: Create a second workflow on a weekly schedule that aggregates 7 days of data and includes charts (generate chart images via an external service like QuickChart).
  • AI summary: Add a GPT Writer node after the Transformer to have AI write a natural-language narrative of the day's metrics: "Signups were up 15% driven by the Product Hunt launch, but conversion dropped..."
  • Team-specific reports: Use a For Each loop to send personalized reports to different team members with data filtered to their region or department.
Tip

Use the Send Email to Yourself node instead of Send Email while building. Once the report looks right, switch to the real recipient list.

Note

Schedule triggers run in UTC by default. Set your timezone explicitly in the trigger configuration to ensure the report arrives at the right local time.