AutomationFlowsGeneral › Generate and Email Markdown Reports

Generate and Email Markdown Reports

Original n8n title: Markdown Report Generation

Markdown Report Generation. Uses manualTrigger, itemLists, markdown, emailSend. Event-driven trigger; 10 nodes.

Event trigger★★★★☆ complexity10 nodesItem ListsEmail SendHTTP RequestMove Binary Data
General Trigger: Event Nodes: 10 Complexity: ★★★★☆ Added:

This workflow follows the Emailsend → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "nodes": [
    {
      "name": "On clicking 'execute'",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        120,
        560
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "name": "SortElements",
      "type": "n8n-nodes-base.itemLists",
      "position": [
        480,
        560
      ],
      "parameters": {
        "options": {},
        "operation": "sort",
        "sortFieldsUi": {
          "sortField": [
            {
              "fieldName": "UserName"
            },
            {
              "fieldName": "TaskTitle"
            },
            {
              "fieldName": "date"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "name": "Markdown",
      "type": "n8n-nodes-base.markdown",
      "position": [
        1340,
        580
      ],
      "parameters": {
        "mode": "markdownToHtml",
        "options": {
          "tables": true,
          "noHeaderId": true,
          "rawHeaderId": false,
          "simpleLineBreaks": true,
          "customizedHeaderId": false,
          "completeHTMLDocument": true
        },
        "markdown": "={{$json[\"mdreport\"]}}"
      },
      "typeVersion": 1
    },
    {
      "name": "CreateMDReport",
      "type": "n8n-nodes-base.function",
      "position": [
        1160,
        580
      ],
      "parameters": {
        "functionCode": "// created report header and custom table style\nvar md_reporthead=\"#Timesheet report\\n\";\nvar md_style =  (`\n<style> table {border: 0.5px solid; border-spacing: 0px;}\n        table th {border-bottom: 0.5px solid;}\n        table thead {background: #D0E4F5;}\n        table tr:nth-child(even) { background: #D8D8D8;}\n</style>\\n\\n`);\n\nvar md_reportbody=md_style+md_reporthead;\n\n//declare several variables that are used for report generation\nvar tablehead = \"| Date | Hours | Task Description |\\n|:---|:---:|---|\\n\";\n\nvar cur_user=\"\";\nvar cur_usernum=0;\n\nvar cur_task=\"\";\nvar cur_tasktotal=0;\n\n\nfor (item of items) {\n  \n  // Check if new user\n  if (item.json.UserName != cur_user) {\n    // Close previous user's task\n    md_reportbody += (cur_tasktotal) ? \"\\n*\"+cur_tasktotal.toFixed(2)+\" - Total hours for this task*\\n\" : \"\";\n    cur_tasktotal = 0; cur_task=\"\";\n\n    // add new user and embed avatar as base64 image\n    cur_user = item.json.UserName;\n    md_reportbody += `\\n##![img](data:image/png;base64,${items[cur_usernum].binary.data.data}) ${cur_user}\\n`;\n    cur_usernum   += 1;\n  } // Check for new user - ENDIF\n\n\n  // Check if new task\n  if (item.json.TaskTitle != cur_task) {\n\n    // if not empty task - add total amount of hours for *previous* task\n    md_reportbody += (cur_tasktotal) ? `\\n*${cur_tasktotal.toFixed(2)} - Total hours for this task*\\n` : \"\";\n\n    // Add new task header and reset total hours counter\n    cur_task = item.json.TaskTitle;\n    md_reportbody += `\\n###${cur_task}\\n${tablehead}`;\n    cur_tasktotal = 0;\n  } // Check for new task - ENDIF\n\n  // Add current task + update total hours\n   md_reportbody += `| ${item.json.date.split('T',1)} | ${item.json.hours.toFixed(2)} | ${item.json.note} |\\n`;\n   cur_tasktotal += item.json.hours;\n}\n\n// Let's not forget the last task's total hours:\nmd_reportbody += (cur_tasktotal) ? `\\n*${cur_tasktotal.toFixed(2)} - Total hours for this task*\\n` : \"\";\n\n// Finalise the report\nmd_reportbody += `\\n*Timesheet report generated on: ${$now.toISODate()}*`;\nmd_reporthead += \"\\n\";\n\nreturn [{mdreport: md_reportbody}];"
      },
      "typeVersion": 1
    },
    {
      "name": "Send Email",
      "type": "n8n-nodes-base.emailSend",
      "disabled": true,
      "position": [
        1760,
        580
      ],
      "parameters": {
        "options": {
          "allowUnauthorizedCerts": false
        },
        "subject": "TimeSheet report",
        "attachments": "data"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "name": "GetImg",
      "type": "n8n-nodes-base.itemLists",
      "position": [
        640,
        760
      ],
      "parameters": {
        "compare": "selectedFields",
        "options": {
          "removeOtherFields": true
        },
        "operation": "removeDuplicates",
        "fieldsToCompare": {
          "fields": [
            {
              "fieldName": "UserAvatar"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "name": "ImgBinary",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        820,
        760
      ],
      "parameters": {
        "url": "={{$json[\"UserAvatar\"]}}",
        "options": {},
        "responseFormat": "file"
      },
      "typeVersion": 2
    },
    {
      "name": "Merge2",
      "type": "n8n-nodes-base.merge",
      "position": [
        980,
        580
      ],
      "parameters": {
        "join": "outer",
        "mode": "mergeByIndex"
      },
      "typeVersion": 1
    },
    {
      "name": "Move Binary Data1",
      "type": "n8n-nodes-base.moveBinaryData",
      "position": [
        1520,
        580
      ],
      "parameters": {
        "mode": "jsonToBinary",
        "options": {
          "fileName": "report.html",
          "mimeType": "text/html",
          "useRawData": true
        },
        "convertAllData": false
      },
      "typeVersion": 1
    },
    {
      "name": "GetTimesheetRecords",
      "type": "n8n-nodes-base.function",
      "position": [
        300,
        560
      ],
      "parameters": {
        "functionCode": "return [{UserName: \"User 1 - Lead Programmer\",\n         UserAvatar: \"https://www.gravatar.com/avatar/?d=robohash&s=32\",\n         TaskTitle: \"Admin\",\n         date: \"2022-05-31T00:00:00.0000000+02:00\",\n         note: \"Creating invoices and submitting timesheets\",\n         hours: 0.5},\n         {UserName: \"User 1 - Lead Programmer\",\n         UserAvatar: \"https://www.gravatar.com/avatar/?d=robohash&s=32\",\n         TaskTitle: \"Admin\",\n         date: \"2022-05-02T00:00:00.0000000+02:00\",\n         note: \"Reporting last month's activity\",\n         hours: 0.5},\n         {UserName: \"User 2 - Designer\",\n         UserAvatar: \"https://www.gravatar.com/avatar/?d=identicon&s=32\",\n         TaskTitle: \"Admin\",\n         date: \"2022-05-30T00:00:00.0000000+02:00\",\n         note: \"Filling timesheets\",\n         hours: 0.5},\n         {UserName: \"User 2 - Designer\",\n         UserAvatar: \"https://www.gravatar.com/avatar/?d=identicon&s=32\",\n         TaskTitle: \"Admin\",\n         date: \"2022-05-03T00:00:00.0000000+02:00\",\n         note: \"Monthly retro meeting\",\n         hours: 0.5},\n         {UserName: \"User 1 - Lead Programmer\",\n         UserAvatar: \"https://www.gravatar.com/avatar/?d=robohash&s=32\",\n         TaskTitle: \"Client 1\",\n         date: \"2022-05-26T00:00:00.0000000+02:00\",\n         note: \"Weekly meeting\",\n         hours: 0.5},\n         {UserName: \"User 1 - Lead Programmer\",\n         UserAvatar: \"https://www.gravatar.com/avatar/?d=robohash&s=32\",\n         TaskTitle: \"Client 1\",\n         date: \"2022-05-05T00:00:00.0000000+02:00\",\n         note: \"Weekly meeting\",\n         hours: 0.5},\n         {UserName: \"User 1 - Lead Programmer\",\n         UserAvatar: \"https://www.gravatar.com/avatar/?d=robohash&s=32\",\n         TaskTitle: \"Client 1\",\n         date: \"2022-05-19T00:00:00.0000000+02:00\",\n         note: \"Weekly meeting\",\n         hours: 0.5},\n         {UserName: \"User 1 - Lead Programmer\",\n         UserAvatar: \"https://www.gravatar.com/avatar/?d=robohash&s=32\",\n         TaskTitle: \"Client 1\",\n         date: \"2022-05-12T00:00:00.0000000+02:00\",\n         note: \"Weekly meeting\",\n         hours: 0.5},\n         {UserName: \"User 1 - Lead Programmer\",\n         UserAvatar: \"https://www.gravatar.com/avatar/?d=robohash&s=32\",\n         TaskTitle: \"Client 1\",\n         date: \"2022-05-12T00:00:00.0000000+02:00\",\n         note: \"Programmed new feature\",\n         hours: 4.5},\n         {UserName: \"User 1 - Lead Programmer\",\n         UserAvatar: \"https://www.gravatar.com/avatar/?d=robohash&s=32\",\n         TaskTitle: \"Client 1\",\n         date: \"2022-05-02T00:00:00.0000000+02:00\",\n         note: \"Updated this and that\",\n         hours: 2.75},\n         {UserName: \"User 2 - Designer\",\n         UserAvatar: \"https://www.gravatar.com/avatar/?d=identicon&s=32\",\n         TaskTitle: \"Client 2\",\n         date: \"2022-05-13T00:00:00.0000000+02:00\",\n         note: \"Designed a new report template\",\n         hours: 6.5},\n         {UserName: \"User 2 - Designer\",\n         UserAvatar: \"https://www.gravatar.com/avatar/?d=identicon&s=32\",\n         TaskTitle: \"Client 2\",\n         date: \"2022-05-23T00:00:00.0000000+02:00\",\n         note: \"Presented the results\",\n         hours: 1.5}\n         ];"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "GetImg": {
      "main": [
        [
          {
            "node": "ImgBinary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge2": {
      "main": [
        [
          {
            "node": "CreateMDReport",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Markdown": {
      "main": [
        [
          {
            "node": "Move Binary Data1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ImgBinary": {
      "main": [
        [
          {
            "node": "Merge2",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "SortElements": {
      "main": [
        [
          {
            "node": "GetImg",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CreateMDReport": {
      "main": [
        [
          {
            "node": "Markdown",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Move Binary Data1": {
      "main": [
        [
          {
            "node": "Send Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GetTimesheetRecords": {
      "main": [
        [
          {
            "node": "SortElements",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On clicking 'execute'": {
      "main": [
        [
          {
            "node": "GetTimesheetRecords",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

How this works

This workflow streamlines the creation and distribution of professional reports by transforming structured data into polished Markdown documents, complete with embedded images, and delivering them directly via email. It's ideal for business analysts, content creators, or teams needing quick, customisable reports without manual formatting hassles. The key step involves using the Markdown node to convert sorted data lists into a cohesive report, enhanced by HTTP requests to fetch and integrate relevant images seamlessly.

Use this workflow when you need automated, event-driven report generation for recurring tasks like weekly summaries or project updates, especially with integrations like emailSend for reliable delivery. Avoid it for highly complex reports requiring AI-driven insights or real-time data processing, as it focuses on static transformations. Common variations include adapting the function node to pull data from external sources or customising the email template for different recipients.

About this workflow

Markdown Report Generation. Uses manualTrigger, itemLists, markdown, emailSend. Event-driven trigger; 10 nodes.

Source: https://github.com/Zie619/n8n-workflows — original creator credit. Request a take-down →

More General workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

General

Google Maps Scraper. Uses manualTrigger, googleSheets, httpRequest, itemLists. Event-driven trigger; 20 nodes.

Google Sheets, HTTP Request, Item Lists
General

Search-Criteria. Uses noOp, stopAndError, itemLists, executeWorkflowTrigger. Event-driven trigger; 14 nodes.

Stop And Error, Item Lists, Execute Workflow Trigger +1
General

Find A New Book Recommendations. Uses manualTrigger, httpRequest, emailSend. Event-driven trigger; 13 nodes.

HTTP Request, Email Send
General

Create 2 Xml Files With And Without Xml Attributes. Uses manualTrigger, mySql, itemLists, xml. Event-driven trigger; 13 nodes.

MySQL, Item Lists, XML +2
General

Read Xml File And Store Content In Google Sheets. Uses manualTrigger, stickyNote, httpRequest, xml. Event-driven trigger; 10 nodes.

HTTP Request, XML, Google Sheets +1