AutomationFlowsEmail & Gmail › Create Gmail Draft from Workflow Trigger

Create Gmail Draft from Workflow Trigger

Original n8n title: Gmail.create_email_draft

gmail.create_email_draft. Uses executeWorkflowTrigger, gmail. Event-driven trigger; 4 nodes.

Event trigger★★★★☆ complexity4 nodesExecute Workflow TriggerGmail
Email & Gmail Trigger: Event Nodes: 4 Complexity: ★★★★☆ Added:

This workflow follows the Execute Workflow Trigger → Gmail 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
{
  "name": "gmail.create_email_draft",
  "nodes": [
    {
      "parameters": {},
      "id": "execute-workflow-trigger",
      "name": "Execute Workflow Trigger",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Input validation and processing - handles flat, nested, or wrapped (subInput)\nconst root = $json ?? {};                       // n8n runs Code per item, so $json is the item\nconst input = root.subInput ?? root;            // support accidental wrap: { subInput: {...} }\n\n// Handle both flat and nested input formats\nconst to = input.to ?? input.params?.to;\nconst subject = input.subject ?? input.params?.subject;\nconst body = input.body ?? input.params?.body;\nconst cc = input.cc ?? input.params?.cc;\nconst bcc = input.bcc ?? input.params?.bcc;\nconst attachments = input.attachments ?? input.params?.attachments;\nconst userId = input.userId ?? input.params?.userId ?? 'me';\nconst connectionId = input.connectionId;\nconst requestId = input.requestId ?? input.params?.requestId ?? null;\nconst tenantId = input.tenantId ?? input.params?.tenantId;\nconst idempotencyKey = input.idempotencyKey ?? input.params?.idempotencyKey;\n\n// Validate required fields\nif (!to) {\n  throw new Error('to field is required');\n}\n\nif (!subject) {\n  throw new Error('subject field is required');\n}\n\nif (!body) {\n  throw new Error('body field is required');\n}\n\nif (!connectionId) {\n  throw new Error('connectionId field is required');\n}\n\n// Validate email addresses\nconst emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst toEmails = Array.isArray(to) ? to : [to];\n\nfor (const email of toEmails) {\n  if (!emailRegex.test(email)) {\n    throw new Error(`Invalid email address: ${email}`);\n  }\n}\n\nif (cc) {\n  const ccEmails = Array.isArray(cc) ? cc : [cc];\n  for (const email of ccEmails) {\n    if (!emailRegex.test(email)) {\n      throw new Error(`Invalid CC email address: ${email}`);\n    }\n  }\n}\n\nif (bcc) {\n  const bccEmails = Array.isArray(bcc) ? bcc : [bcc];\n  for (const email of bccEmails) {\n    if (!emailRegex.test(email)) {\n      throw new Error(`Invalid BCC email address: ${email}`);\n    }\n  }\n}\n\n// Build MIME message\nfunction buildMimeMessage() {\n  const boundary = `----=_Part_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n  let mimeMessage = '';\n  \n  // Headers\n  mimeMessage += `To: ${toEmails.join(', ')}\\r\\n`;\n  if (cc) {\n    const ccEmails = Array.isArray(cc) ? cc : [cc];\n    mimeMessage += `Cc: ${ccEmails.join(', ')}\\r\\n`;\n  }\n  if (bcc) {\n    const bccEmails = Array.isArray(bcc) ? bcc : [bcc];\n    mimeMessage += `Bcc: ${bccEmails.join(', ')}\\r\\n`;\n  }\n  mimeMessage += `Subject: ${subject}\\r\\n`;\n  mimeMessage += `Content-Type: text/html; charset=utf-8\\r\\n`;\n  mimeMessage += `\\r\\n`;\n  mimeMessage += `${body}\\r\\n`;\n  \n  // Convert to base64url for Gmail API\n  const base64Message = Buffer.from(mimeMessage).toString('base64');\n  return base64Message.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n\nconst mimeMessage = buildMimeMessage();\n\nreturn {\n  json: {\n    userId: userId,\n    tenantId: tenantId,\n    requestId: requestId,\n    connectionId: connectionId,\n    idempotencyKey: idempotencyKey,\n    \n    // >>> ADD THESE FOR GMAIL NODE UI <<<\n    to,                   // may be string or array\n    toEmails,             // always array\n    subject,\n    body,                 // text or HTML\n    cc: cc || null,       // optional, string|array|null\n    bcc: bcc || null,     // optional, string|array|null\n    \n    // keep for future raw usage\n    mimeMessage: mimeMessage\n  }\n};"
      },
      "id": "input-validation",
      "name": "Input Validation & MIME Building",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "resource": "draft",
        "operation": "create",
        "userId": "={{ $json.userId }}",
        "to": "={{ Array.isArray($('Input Validation & MIME Building').item.json.toEmails) ? $('Input Validation & MIME Building').item.json.toEmails.join(', ') : ($('Input Validation & MIME Building').item.json.to || '') }}",
        "subject": "={{ $('Input Validation & MIME Building').item.json.subject }}",
        "emailType": "html",
        "message": "={{ $('Input Validation & MIME Building').item.json.body }}",
        "additionalFields": {
          "ccList": "={{ Array.isArray($('Input Validation & MIME Building').item.json.cc) ? $('Input Validation & MIME Building').item.json.cc.join(', ') : ($('Input Validation & MIME Building').item.json.cc || '') }}",
          "bccList": "={{ Array.isArray($('Input Validation & MIME Building').item.json.bcc) ? $('Input Validation & MIME Building').item.json.bcc.join(', ') : ($('Input Validation & MIME Building').item.json.bcc || '') }}"
        }
      },
      "id": "gmail-create-draft",
      "name": "Gmail Create Draft",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "credentials": {
        "gmailOAuth2": "<your credential>"
      }
    },
    {
      "parameters": {
        "jsCode": "// Response formatting with proper error handling\nconst input = $json;\nconst validationData = $node[\"Input Validation & MIME Building\"].json;\nconst timestamp = new Date().toISOString();\nconst brick = 'gmail.create_email_draft';\nconst brickVersion = 'v1';\n\ntry {\n  if (input.error) {\n    // Handle error from Gmail API\n    const error = input.error;\n    let errorCode = 'GMAIL_API_ERROR';\n    let retryable = false;\n    let retryAfterMs;\n    \n    if (error.httpCode) {\n      switch (error.httpCode) {\n        case 400:\n          errorCode = 'INVALID_REQUEST';\n          break;\n        case 401:\n          errorCode = 'AUTHENTICATION_FAILED';\n          break;\n        case 403:\n          errorCode = 'PERMISSION_DENIED';\n          break;\n        case 429:\n          errorCode = 'RATE_LIMIT_EXCEEDED';\n          retryable = true;\n          retryAfterMs = 60000;\n          break;\n        case 500:\n        case 502:\n        case 503:\n        case 504:\n          errorCode = 'SERVER_ERROR';\n          retryable = true;\n          retryAfterMs = 5000;\n          break;\n        default:\n          errorCode = 'GMAIL_API_ERROR';\n      }\n    }\n    \n    return {\n      json: {\n        ok: false,\n        brick,\n        brickVersion,\n        timestamp,\n        requestId: validationData.requestId,\n        error: {\n          code: errorCode,\n          message: error.message || 'Gmail API error occurred',\n          retryable,\n          retryAfterMs,\n          details: {\n            httpCode: error.httpCode,\n            service: 'gmail',\n            operation: 'create_draft'\n          }\n        }\n      }\n    };\n  }\n  \n  // Success response\n  const gmailResponse = input;\n  \n  return {\n    json: {\n      ok: true,\n      brick,\n      brickVersion,\n      timestamp,\n      requestId: validationData.requestId,\n      cached: false,\n      data: {\n        draftId: gmailResponse.id,\n        message: {\n          id: gmailResponse.message?.id,\n          threadId: gmailResponse.message?.threadId,\n          snippet: gmailResponse.message?.snippet\n        }\n      }\n    }\n  };\n  \n} catch (error) {\n  // Handle unexpected errors\n  return {\n    json: {\n      ok: false,\n      brick,\n      brickVersion,\n      timestamp,\n      requestId: validationData.requestId || null,\n      error: {\n        code: 'INTERNAL_ERROR',\n        message: error.message || 'Internal processing error',\n        retryable: false,\n        details: {\n          service: 'gmail',\n          operation: 'create_draft'\n        }\n      }\n    }\n  };\n}"
      },
      "id": "response-formatter",
      "name": "Response Formatter",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        300
      ]
    }
  ],
  "connections": {
    "Execute Workflow Trigger": {
      "main": [
        [
          {
            "node": "Input Validation & MIME Building",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Input Validation & MIME Building": {
      "main": [
        [
          {
            "node": "Gmail Create Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Create Draft": {
      "main": [
        [
          {
            "node": "Response Formatter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "1",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "gmail-create-draft-v1"
}

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

About this workflow

gmail.create_email_draft. Uses executeWorkflowTrigger, gmail. Event-driven trigger; 4 nodes.

Source: https://github.com/SammyTourani/Pulse/blob/09d51f209c603477a489582b13f5c08d9a0af370/flows/bricks/gmail/gmail.create_email_draft.json — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

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

Email & Gmail

Splitout Code. Uses manualTrigger, httpRequest, stickyNote, splitOut. Event-driven trigger; 46 nodes.

HTTP Request, Execute Workflow Trigger, Gmail +1
Email & Gmail

Automate CSV imports into HubSpot without the mess. Powered by n8n. Supercharged by Pollup AI.

HTTP Request, Execute Workflow Trigger, Gmail +1
Email & Gmail

Echo Brand Voice Analysis (Processor) - TASK-074 Dec 10 Fix. Uses formTrigger, httpRequest, executeWorkflowTrigger, moveBinaryData. Event-driven trigger; 40 nodes.

Form Trigger, HTTP Request, Execute Workflow Trigger +2
Email & Gmail

Code Filter. Uses googleSheets, gmail, stickyNote, executeWorkflowTrigger. Event-driven trigger; 32 nodes.

Google Sheets, Gmail, Execute Workflow Trigger
Email & Gmail

This n8n workflow enables teams to automate and standardize multi-step onboarding or messaging workflows using Google Sheets, Forms, Gmail, and dynamic logic powered by Code and Switch nodes. It ensur

Google Sheets, Form, Execute Workflow Trigger +2