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
- Create a new workflow → + Add Trigger → Schedule
- Configure:
| Field | Value |
|---|---|
| Frequency | Daily |
| Time | 08:00 |
| Timezone | Your team's timezone (e.g., America/New_York) |
| Days | Monday through Friday (skip weekends) |
- Click Test to simulate a trigger fire; this populates the trigger output with a timestamp
Step 2: Pull data with HTTP Request
- Click + Add Node → Flow Control → HTTP Request
- Configure:
| Field | Value |
|---|---|
| Method | GET |
| URL | Your API endpoint (see examples below) |
| Headers | Authorization: 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:
| Field | Value |
|---|---|
| Table | Your metrics table |
| Filter | created_at >= {{TODAY_START()}} |
| Sort | created_at DESC |
- Test the HTTP Request to see the response structure
- Save
Step 3: Transform the data into a report
- Click + Add Node → Flow Control → Transformer
- 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;
- Test the transformer: verify the HTML output looks right
- Save
Step 4: Send the email
- Click + Add Node → Flow Control → Send Email
- Configure:
| Field | Value |
|---|---|
| To | team@yourcompany.com (or comma-separated list) |
| Subject | {{transformer.result.subject}} |
| Body | {{transformer.result.body}} |
- Test: check your inbox for the formatted report
- Save
Step 5: Test the full workflow
- Click Run in the top bar to execute the entire chain
- 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.