This workflow corresponds to n8n.io template #17967 — we link there as the canonical source.
This workflow follows the Gmail → Google Sheets 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 →
{
"name": "Generate and email course certificates from Google Sheets with Canva and Gmail",
"nodes": [
{
"id": "0b9c003c-287e-4131-9684-5f1ea6c33dfc",
"name": "About this template",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
240
],
"parameters": {
"width": 460,
"height": 952,
"content": "# \ud83c\udf93 Generate and email course certificates from Google Sheets with Canva and Gmail\n\n**Who's it for**\nTraining providers, HR/onboarding teams, course platforms and event organisers on Canva Enterprise who issue personalised certificates at scale.\n\n**How it works**\n1. A coordinator fills in a row (certificate ID, full name, date, course, instructor, email) and sets `ready` to *`YES`*.\n2. Data Autofill fills your certificate brand template with the row's details.\n3. The design is exported as a PDF and downloaded.\n4. The certificate is emailed to the recipient as a **PDF attachment** via Gmail.\n5. The row is updated with the design link and status *`SENT`*.\n\n**Setup**\n- [ ] Create a Google Sheet with columns: `ready`, `certificate_id`, `full_name`, `full_date`, `course_name`, `instructor_name`, `email`, `design_url`, `status`.\n- [ ] In Canva, create a certificate brand template with a dataset: `full_name`, `full_date`, `course_name`, `instructor_name` (all text).\n- [ ] Enter your brand template ID in **Workflow configuration**.\n- [ ] Connect your Google, Canva and Gmail credentials.\n\n**Requirements**\n- Canva Enterprise account (Data Autofill is Enterprise-only)\n- The **verified** community node `@canva/n8n-nodes-canva` (officially provided by Canva)\n- Gmail credential (swap for SMTP or Outlook if preferred)"
},
"typeVersion": 1
},
{
"id": "8af70fe2-3a77-4867-8ad5-7af61c03191e",
"name": "Note \u2014 trigger",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
240
],
"parameters": {
"color": 7,
"width": 472,
"height": 948,
"content": "## Google Sheets trigger\n\nThe trigger watches **only the `ready` column** (\u201cColumns to Watch\u201d), and the IF node only lets a row through when `ready` is exactly `YES` \u2014 so half-filled rows never fire.\n\nAfter the certificate is sent, the workflow sets `ready` to `DONE`; a row is never processed twice. To re-send a certificate, set `ready` back to `YES`."
},
"typeVersion": 1
},
{
"id": "6b3041e2-6196-4214-9912-58047b783151",
"name": "Note \u2014 configuration",
"type": "n8n-nodes-base.stickyNote",
"position": [
1152,
240
],
"parameters": {
"color": 7,
"width": 300,
"height": 948,
"content": "## Workflow configuration\n\n`brand_template_id`: the ID of your certificate brand template (Enterprise plan), containing the dataset fields listed on the yellow sticky. Find it in the URL: *`https://www.canva.com/brand/brand-templates/X`*\n\n`spreadsheet_id` / `sheet_id`: derived **automatically** from the trigger's settings \u2014 leave them as they are. Every later node reads the spreadsheet from here, so the sheet is only ever configured once, in the trigger.\n\n`full_date`: normalised here. The Google Sheets trigger can [render dates as numbers](https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.googlesheetstrigger/common-issues#date-and-time-columns-are-rendering-as-numbers), so this expression converts serials to text like `5 August 2026` - Plain-text dates are unchanged. Adjust the locale (`en-GB`) and format options to your taste."
},
"typeVersion": 1
},
{
"id": "2cc0c45e-231c-460f-8f20-6164783c045a",
"name": "Note \u2014 Canva",
"type": "n8n-nodes-base.stickyNote",
"position": [
1488,
240
],
"parameters": {
"color": 7,
"width": 492,
"height": 944,
"content": "## Create & export the certificate\n\nData Autofill fills the brand template with the row's details \u2014 field names in the Autofill node must match your template's dataset **exactly**. Autofill replaces each tagged element's *entire* text, so keep static labels (e.g. \u201cDate:\u201d) out of tagged elements.\n\nThe design is then exported as a PDF and downloaded, because export links **expire after 24 hours** \u2014 attaching the file means the recipient's copy never dies.\n\n[Guide on Autofill and Brand Templates](https://www.canva.dev/docs/connect/autofill-guide/)"
},
"typeVersion": 1
},
{
"id": "2f06fa97-c872-4784-86b7-223caa0a2a9d",
"name": "Note \u2014 email",
"type": "n8n-nodes-base.stickyNote",
"position": [
2016,
240
],
"parameters": {
"color": 7,
"width": 448,
"height": 944,
"content": "## Email the recipient\n\nGmail sends the certificate to the `email` address from the row, with the PDF attached.\n\nPersonalise the subject and body in this node \u2014 or swap it for **SMTP (Send Email)** or **Microsoft Outlook** without touching the rest of the workflow."
},
"typeVersion": 1
},
{
"id": "b95f3f38-d4ee-43e8-99b9-487b5a2cd90e",
"name": "Note \u2014 write back",
"type": "n8n-nodes-base.stickyNote",
"position": [
2496,
240
],
"parameters": {
"color": 7,
"width": 396,
"height": 944,
"content": "## Write results back\n\nOn success the row is updated:\n\n`ready`: *`DONE`*\n`design_url`: *`{{URL_FROM_PREVIOUS_STEP}}`*\n`status`: *`SENT`*\n\nIf any Canva or email step fails, the row is marked as:\n\n`ready`: *`ERROR`*\n`status`: *`{{THE_ERROR_MESSAGE}}`*\n\nTo retry: fix the cause, then set `ready` back to *`YES`*."
},
"typeVersion": 1
},
{
"id": "07a7fa31-82d7-45c4-9155-77ee12fed904",
"name": "On Sheet row update",
"type": "n8n-nodes-base.googleSheetsTrigger",
"position": [
720,
976
],
"parameters": {
"event": "anyUpdate",
"options": {
"columnsToWatch": [
"ready"
]
},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": ""
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/YOUR_DOCUMENT_ID/edit"
}
},
"typeVersion": 1
},
{
"id": "55de4e45-4fcb-45a6-a5c3-266635ea0c94",
"name": "Row marked ready?",
"type": "n8n-nodes-base.if",
"position": [
912,
976
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-ready-01",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ ($json.ready ?? '').toString().trim().toUpperCase() }}",
"rightValue": "YES"
}
]
}
},
"typeVersion": 2
},
{
"id": "8a7b2275-a78d-4f92-8902-b8b6cebb2ba3",
"name": "Create certificate from brand template",
"type": "@canva/n8n-nodes-canva.canva",
"onError": "continueErrorOutput",
"position": [
1568,
960
],
"parameters": {
"data": "={{ JSON.stringify({\n full_name: { type: 'text', text: ($('Workflow configuration').item.json.full_name ?? '').toString() },\n full_date: { type: 'text', text: ($('Workflow configuration').item.json.full_date ?? '').toString() },\n course_name: { type: 'text', text: ($('Workflow configuration').item.json.course_name ?? '').toString() },\n instructor_name: { type: 'text', text: ($('Workflow configuration').item.json.instructor_name ?? '').toString() }\n}) }}",
"title": "={{ 'Certificate \u2014 ' + $('Workflow configuration').item.json.full_name + ' \u2014 ' + $('Workflow configuration').item.json.course_name }}",
"resource": "autofill",
"brandTemplateId": "={{ $('Workflow configuration').item.json.brand_template_id }}"
},
"typeVersion": 1
},
{
"id": "7f0caac3-d6c5-4cad-8d7d-a9af3754f866",
"name": "Export certificate as PDF",
"type": "@canva/n8n-nodes-canva.canva",
"onError": "continueErrorOutput",
"position": [
1792,
960
],
"parameters": {
"maxWait": 120,
"designId": "={{ $json.job.result.design.id }}",
"resource": "export"
},
"typeVersion": 1
},
{
"id": "8abb64f2-4cc9-48bc-9cda-802fae660d39",
"name": "Download the PDF",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
2096,
960
],
"parameters": {
"url": "={{ $json.job.urls[0] }}",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
}
},
"typeVersion": 4.2
},
{
"id": "8423e930-af86-44aa-ae52-9251ace80139",
"name": "Email certificate to recipient",
"type": "n8n-nodes-base.gmail",
"onError": "continueErrorOutput",
"position": [
2320,
960
],
"parameters": {
"sendTo": "={{ $('Workflow configuration').item.json.email }}",
"message": "=<p>Hi {{ $('Workflow configuration').item.json.full_name }},</p><p>Congratulations on completing <strong>{{ $('Workflow configuration').item.json.course_name }}</strong>!</p><p>Your certificate is attached as a PDF. Keep it safe \u2014 and feel free to share it.</p><p>Best,<br>{{ $('Workflow configuration').item.json.instructor_name }}</p>",
"options": {
"attachmentsUi": {
"attachmentsBinary": [
{
"property": "data"
}
]
},
"appendAttribution": false
},
"subject": "=Your certificate \u2014 {{ $('Workflow configuration').item.json.course_name }}"
},
"typeVersion": 2.1
},
{
"id": "925aa173-8c27-40bf-9477-8e7389ec6f39",
"name": "Mark row as errored",
"type": "n8n-nodes-base.googleSheets",
"position": [
2640,
976
],
"parameters": {
"columns": {
"value": {
"ready": "ERROR",
"status": "={{ 'Step failed: ' + ($json.error?.message ?? 'check the execution log in n8n') }}",
"certificate_id": "={{ $('Workflow configuration').item.json.certificate_id }}"
},
"schema": [
{
"id": "ready",
"type": "string",
"display": true,
"required": false,
"displayName": "ready",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "certificate_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "certificate_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "full_name",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "full_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "full_date",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "full_date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "course_name",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "course_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "instructor_name",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "instructor_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "email",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "design_url",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "design_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"certificate_id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "id",
"value": "={{ $('Workflow configuration').item.json.sheet_id }}"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Workflow configuration').item.json.spreadsheet_id }}"
}
},
"typeVersion": 4.5
},
{
"id": "323c165c-c5de-4fa9-90dd-f0fa15fa1c84",
"name": "Workflow configuration",
"type": "n8n-nodes-base.set",
"position": [
1248,
960
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "cfg-0001",
"name": "brand_template_id",
"type": "string",
"value": "YOUR_BRAND_TEMPLATE_ID"
},
{
"id": "cfg-0002",
"name": "spreadsheet_id",
"type": "string",
"value": "={{ ($('On Sheet row update').params.documentId.value || '').toString().match(/[-\\w]{25,}/)?.[0] }}"
},
{
"id": "cfg-0003",
"name": "sheet_id",
"type": "string",
"value": "={{ $('On Sheet row update').params.sheetName.value }}"
},
{
"id": "cfg-0004",
"name": "full_date",
"type": "string",
"value": "={{ ($json.full_date ?? '').toString().trim() === '' ? '' : (isNaN(Number($json.full_date)) ? $json.full_date.toString() : new Date(Date.UTC(1899, 11, 30) + Math.round(Number($json.full_date) * 86400000)).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' })) }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "2b363797-7d20-4f05-abc3-85f3eea5455a",
"name": "Mark row as completed",
"type": "n8n-nodes-base.googleSheets",
"position": [
2640,
720
],
"parameters": {
"columns": {
"value": {
"ready": "DONE",
"status": "SENT",
"design_url": "={{ $('Create certificate from brand template').item.json.job.result.design.url }}",
"certificate_id": "={{ $('Workflow configuration').item.json.certificate_id }}"
},
"schema": [
{
"id": "ready",
"type": "string",
"display": true,
"required": false,
"displayName": "ready",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "certificate_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "certificate_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "full_name",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "full_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "full_date",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "full_date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "course_name",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "course_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "instructor_name",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "instructor_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "email",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "design_url",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "design_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"certificate_id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "id",
"value": "={{ $('Workflow configuration').item.json.sheet_id }}"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Workflow configuration').item.json.spreadsheet_id }}"
}
},
"typeVersion": 4.5
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"connections": {
"Download the PDF": {
"main": [
[
{
"node": "Email certificate to recipient",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark row as errored",
"type": "main",
"index": 0
}
]
]
},
"Row marked ready?": {
"main": [
[
{
"node": "Workflow configuration",
"type": "main",
"index": 0
}
]
]
},
"On Sheet row update": {
"main": [
[
{
"node": "Row marked ready?",
"type": "main",
"index": 0
}
]
]
},
"Workflow configuration": {
"main": [
[
{
"node": "Create certificate from brand template",
"type": "main",
"index": 0
}
]
]
},
"Export certificate as PDF": {
"main": [
[
{
"node": "Download the PDF",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark row as errored",
"type": "main",
"index": 0
}
]
]
},
"Email certificate to recipient": {
"main": [
[
{
"node": "Mark row as completed",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark row as errored",
"type": "main",
"index": 0
}
]
]
},
"Create certificate from brand template": {
"main": [
[
{
"node": "Export certificate as PDF",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark row as errored",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow watches a Google Sheet for rows marked ready, generates a personalized certificate in Canva using a brand template, exports and downloads it as a PDF, then emails it to the recipient via Gmail and updates the sheet with the delivery status. Triggers when the column…
Source: https://n8n.io/workflows/17967/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Automatically processes new orders added to Google Sheets. Small orders are approved instantly; large orders trigger an HTML email with one-click Approve / Reject links — each handled by an independen
Fluidflow Licensing Framework. Uses httpRequest, xero, googleSheetsTrigger, slack. Event-driven trigger; 25 nodes.
This workflow monitors a Google Sheets billing queue, creates Mercado Pago payment links for new invoices, emails customers via Gmail, and logs sent links back to Google Sheets, while also handling Me
This workflow watches Google Sheets for new rows, renders a personalized video with iLoveVideoEditor using the row data, uploads the finished MP4 to Google Drive, writes the Drive link back to the sam
Hiring teams often struggle with document follow-ups, offer letter generation, and stakeholder communication. Manual checks, email back-and-forth, and missing files slow down hiring and create chaos d