Monthly revenue report by email
On the 1st of each month, get last month's Stripe revenue compared with the month before.
How it works
Trigger: Schedule — 1st of every month at 8:00
- Get recent charges from Stripe.
- Add up successful charges for last month and the month before.
- Email yourself both totals and the growth percentage.
Diagram
Trigger App Code
Code
JavaScript used in the Code step — already included in the template below.
// Compares last month's successful charges with the month before.
const now = new Date();
const startOf = (monthsAgo) => new Date(now.getFullYear(), now.getMonth() - monthsAgo, 1).getTime() / 1000;
const lastStart = startOf(1);
const thisStart = startOf(0);
const prevStart = startOf(2);
const charges = $input.all().map((item) => item.json).filter((c) => c.status === "succeeded" && c.paid);
const total = (from, to) =>
charges.filter((c) => c.created >= from && c.created < to).reduce((sum, c) => sum + c.amount / 100, 0);
const last = total(lastStart, thisStart);
const prev = total(prevStart, lastStart);
const growth = prev ? Math.round(((last - prev) / prev) * 100) : 0;
const month = new Date(lastStart * 1000).toLocaleString("en-US", { month: "long", year: "numeric" });
return [{
json: {
subject: `Revenue report — ${month}`,
body:
`${month}: ${last.toFixed(2)}\n` +
`Month before: ${prev.toFixed(2)}\n` +
`Growth: ${growth >= 0 ? "▲" : "▼"} ${Math.abs(growth)}%`,
},
}];Build it
Pick your tool — same automation, step by step.
Copy and paste straight into an n8n canvas (Ctrl+V), or download and use Import from File. Then connect your own credentials and replace every YOUR_… placeholder.
Show code (131 lines)
{
"name": "Monthly revenue report by email (Flowngn)",
"nodes": [
{
"parameters": {
"content": "## Monthly revenue report by email\nOn the 1st of each month, get last month's Stripe revenue compared with the month before.\n\n**Setup**\n1. Open each node and connect your own credentials.\n2. Replace every YOUR_… placeholder.\n3. Test once, then activate.\n\nFree template from Flowngn",
"height": 260,
"width": 340
},
"id": "2f780998-9838-445d-a923-1c16b94b02ed",
"name": "Setup notes",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-420,
-60
]
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "months",
"triggerAtDayOfMonth": 1,
"triggerAtHour": 8
}
]
}
},
"id": "2f780998-9838-445d-a923-1c16b94b02ed",
"name": "1st of month 8:00",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
0,
0
]
},
{
"parameters": {
"resource": "charge",
"operation": "getAll",
"returnAll": true
},
"id": "2f780998-9838-445d-a923-1c16b94b02ed",
"name": "Get charges",
"type": "n8n-nodes-base.stripe",
"typeVersion": 1,
"position": [
300,
0
]
},
{
"parameters": {
"jsCode": "// Compares last month's successful charges with the month before.\nconst now = new Date();\nconst startOf = (monthsAgo) => new Date(now.getFullYear(), now.getMonth() - monthsAgo, 1).getTime() / 1000;\n\nconst lastStart = startOf(1);\nconst thisStart = startOf(0);\nconst prevStart = startOf(2);\n\nconst charges = $input.all().map((item) => item.json).filter((c) => c.status === \"succeeded\" && c.paid);\nconst total = (from, to) =>\n charges.filter((c) => c.created >= from && c.created < to).reduce((sum, c) => sum + c.amount / 100, 0);\n\nconst last = total(lastStart, thisStart);\nconst prev = total(prevStart, lastStart);\nconst growth = prev ? Math.round(((last - prev) / prev) * 100) : 0;\nconst month = new Date(lastStart * 1000).toLocaleString(\"en-US\", { month: \"long\", year: \"numeric\" });\n\nreturn [{\n json: {\n subject: `Revenue report — ${month}`,\n body:\n `${month}: ${last.toFixed(2)}\\n` +\n `Month before: ${prev.toFixed(2)}\\n` +\n `Growth: ${growth >= 0 ? \"▲\" : \"▼\"} ${Math.abs(growth)}%`,\n },\n}];"
},
"id": "2f780998-9838-445d-a923-1c16b94b02ed",
"name": "Compare months",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
600,
0
]
},
{
"parameters": {
"sendTo": "you@yourcompany.com",
"subject": "={{ $json.subject }}",
"emailType": "text",
"message": "={{ $json.body }}",
"options": {
"appendAttribution": false
}
},
"id": "2f780998-9838-445d-a923-1c16b94b02ed",
"name": "Email report",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
900,
0
]
}
],
"connections": {
"1st of month 8:00": {
"main": [
[
{
"node": "Get charges",
"type": "main",
"index": 0
}
]
]
},
"Get charges": {
"main": [
[
{
"node": "Compare months",
"type": "main",
"index": 0
}
]
]
},
"Compare months": {
"main": [
[
{
"node": "Email report",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"meta": {
"templateCredsSetupCompleted": false
},
"tags": []
}In Make, create a new scenario and add these modules in order:
- Schedule the scenario: 1st of month 8:00
- Stripe › List Charges
- Tools › Set variable / Iterator + Array aggregator (same logic as the Code step)
- Gmail › Send an Email
- Every module run uses Make operations — the free plan includes a monthly quota.
- Make's built-in functions (map, sum, formatDate…) can replace most Code steps; for complex logic use a code module.
- Module and step names can differ slightly as Make and Zapier update their apps.
In Zapier, create a new Zap and add these steps in order:
- Schedule by Zapier — 1st of month 8:00
- Code by Zapier — list charges via the Stripe API
- Code by Zapier — Run JavaScript (paste the code from the Code section)
- Gmail — Send Email
- Zaps with more than two steps need a paid Zapier plan.
- Webhooks, Filters/Paths and Code by Zapier are premium features on some Zapier plans.
- Code by Zapier runs JavaScript: reuse the code shown above, reading inputs from inputData instead of $json.
- Module and step names can differ slightly as Make and Zapier update their apps.
Tips
- For more than a few hundred payments a month, use Stripe's reporting exports instead.
Related flows
Finance & AdminEasy
Instant alert when a Stripe payment fails
Know about failed payments right away and log them for follow-up.
Stripe → Slack → Google Sheets2h/mo
Finance & AdminEasy
Paid invoices into your bookkeeping sheet
Every paid Stripe invoice is recorded automatically, ready for your accountant.
Stripe → Google Sheets5h/mo
Finance & AdminEasy
Save invoice attachments from Gmail to Drive
Invoices that arrive by email are filed into a Drive folder automatically.
Gmail → Google Drive3h/mo