This workflow corresponds to n8n.io template #13077 — we link there as the canonical source.
This workflow follows the Gmail → Google Drive 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 →
{
"id": "m3PR6pVKF4OpMgEr",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Send job applications with Telegram, OpenAI, and Gmail",
"tags": [
{
"id": "L1ONQkjTRkmEpb1L",
"name": "Prod",
"createdAt": "2026-01-29T09:38:03.164Z",
"updatedAt": "2026-01-29T09:38:03.164Z"
}
],
"nodes": [
{
"id": "b07613f6-04cc-46e2-a105-11190f1c229f",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-624,
320
],
"parameters": {
"width": 500,
"height": 520,
"content": "## How it works\n\nThis workflow automates job applications through Telegram. Send a screenshot of any job posting to your Telegram bot, and AI will extract the job details, match them to your resume, and generate a personalized application email.\n\nThe bot analyzes the job requirements using OpenAI's vision API, compares them with your resume stored in OpenAI Files, then drafts a tailored email in the job's language. You review and approve the draft before it's sent via Gmail with your resume attached.\n\n## Setup steps\n\n1. Upload your resume to OpenAI Files API and note the file_id\n2. Upload your resume to Google Drive and note the file_id\n3. Create a Telegram bot via @BotFather and save the token\n4. Configure credentials in n8n: Telegram, OpenAI, Redis, Gmail, Google Drive\n5. Update the PayloadForReply node with your OpenAI resume file_id\n6. Update both Download Resume nodes with your Google Drive file_id\n7. Activate the workflow and send a job post screenshot to your bot"
},
"typeVersion": 1
},
{
"id": "7230dd49-f98e-4659-8562-109582963196",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-64,
320
],
"parameters": {
"color": 7,
"width": 347,
"height": 220,
"content": "## Input routing\n\nReceives job post images or callback confirmations from Telegram and routes to the appropriate path"
},
"typeVersion": 1
},
{
"id": "a0a21348-1d90-406b-bfb2-dcd1329f5121",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
432,
256
],
"parameters": {
"color": 7,
"width": 492,
"height": 140,
"content": "## AI analysis and email generation\n\nExtracts job details from image, matches with resume, and generates personalized application email"
},
"typeVersion": 1
},
{
"id": "bd5b5afa-1233-4223-b10d-f4d3b411513d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1104,
256
],
"parameters": {
"color": 7,
"width": 480,
"height": 140,
"content": "## Draft storage and user confirmation\n\nStores email draft in Redis and sends preview to user with approval button"
},
"typeVersion": 1
},
{
"id": "a76700c9-8fcb-46d0-8de3-4196a198f273",
"name": "Telegram Trigger",
"type": "n8n-nodes-base.telegramTrigger",
"position": [
-48,
624
],
"parameters": {
"updates": [
"message",
"callback_query"
],
"additionalFields": {
"download": true
}
},
"typeVersion": 1.2
},
{
"id": "68bcb3ea-c5fa-4618-b5d9-8d1438d61e6c",
"name": "Build AI request payload",
"type": "n8n-nodes-base.code",
"position": [
624,
432
],
"parameters": {
"jsCode": "// ---------- Inputs ----------\n/**\n * Expects a Telegram update upstream.\n * - Image is in binary (first binary key)\n * - Caption (optional) at $.message.caption or $.caption\n * - Optional \"insights\" (tone/length hints) at $.insights\n * Provide your uploaded resume file_id below.\n */\nconst inputItem = $('Telegram Trigger').first();\n\n// Detect first binary (photo)\nconst firstBinaryKey = Object.keys(inputItem.binary || {})[0];\nif (!firstBinaryKey) {\n throw new Error(\"No binary image found in input. Connect the job post image.\");\n}\nconst jobImageBinary = inputItem.binary[firstBinaryKey].data;\nconst jobImageMime = inputItem.binary[firstBinaryKey].mimeType || \"image/jpeg\";\nconst jobImageBase64 = `data:${jobImageMime};base64,${jobImageBinary}`;\n\n// Telegram caption (pass through verbatim; do NOT parse or regex)\nconst caption =\n inputItem.json?.message?.caption ??\n inputItem.json?.caption ??\n \"\";\n\n// Optional extra guidance from upstream\nconst insights = (inputItem.json?.insights ?? \"\").toString();\n\n// ---------- Config ----------\nconst MODEL = \"gpt-5.2\";\nconst resumeFileId = \"YOUR_OPENAI_FILE_ID_HERE\"; // <-- replace with your own file_id\nif (!resumeFileId?.startsWith(\"file-\")) {\n throw new Error(\"Invalid resume file_id. Upload your resume to OpenAI and update the constant.\");\n}\n\n// ---------- Prompts ----------\nconst systemPrompt = `\nYou are a job-application assistant.\n\nOnly pick ONE job offer to process \u2014 the one most clearly related to Data roles\n(Data Engineer, Data Analyst, Data Scientist, ML/AI, Analytics, BI, etc.) and best matching the attached resume.\n\nPriority of sources:\nP1) CAPTION (if provided) \u2014 treat as highest-priority instructions/hints.\n If CAPTION says to use a particular email (e.g., \"send to this email ...\"),\n use that email for recruiter_email even if the image shows a different one.\nP2) Job post IMAGE content.\nP3) Resume.\n\nTasks:\n1) Read the job post IMAGE and extract fields into the provided JSON schema.\n2) Use the CAPTION (P1) to override/guide decisions, including recruiter_email selection.\n3) Read the resume and identify:\n - The most relevant past experiences,\n - Concrete achievements,\n - Technical skills and tools,\n - Business impact that aligns with the job post.\n4) Using those findings, draft a concise, professional outreach email tailored to the role.\n - Explicitly highlight a few resume-backed experiences that match the job post.\n - Integrate them naturally (no lists, no bullet points).\n - Refine the language for clarity, professionalism, and human tone.\n5) Detect the job language and write the email in that language (FR post \u2192 FR email; else EN).\n\nOutput rules:\n- Be concise, warm, confident (no buzzwords, no generic AI clich\u00e9s).\n- Only claim skills and experiences supported by the resume.\n- If a field is missing, return \"\" for strings or [] for arrays (no hallucination).\n- Email body: ~120\u2013180 words unless INSIGHTS specify otherwise.\n- Emphasize relevance: relate my previous work directly to the job responsibilities.\n- Subject: ~6\u201310 words.\n- Plain text only (no markdown).\n`;\n\nconst userPrompt = `\nFrom the job image:\n- Extract all fields exactly as defined in the schema.\n- Recruiter email selection order:\n 1) Anything instructed in CAPTION (highest priority).\n 2) Otherwise use the recruiter/application email from the IMAGE.\n 3) Otherwise set recruiter_email to \"\".\n\nThen, using my resume:\n- Identify my most relevant past experiences and skills that match this role.\n- Incorporate those experiences naturally into the outreach email to show clear alignment.\n- Highlight achievements only if they appear in the resume.\n- Refine the email for clarity, flow, and professionalism.\n- Follow INSIGHTS (tone/length). If CAPTION and INSIGHTS conflict, CAPTION wins.\n\nReturn ONLY the JSON according to the schema (no extra text).\n`;\n\n// ---------- Build /v1/responses payload ----------\nconst payload = {\n model: MODEL,\n input: [\n {\n role: \"system\",\n content: [{ type: \"input_text\", text: systemPrompt.trim() }]\n },\n {\n role: \"user\",\n content: [\n { type: \"input_text\", text: userPrompt.trim() },\n\n // Provide CAPTION and INSIGHTS verbatim (no parsing)\n { type: \"input_text\", text: `CAPTION (P1): ${caption || \"(none)\"}` },\n { type: \"input_text\", text: `INSIGHTS: ${insights || \"(none)\"}` },\n\n // Image to parse\n { type: \"input_image\", image_url: jobImageBase64 },\n\n // Resume file reference\n { type: \"input_file\", file_id: resumeFileId }\n ]\n }\n ],\n text: {\n format: {\n type: \"json_schema\",\n name: \"job_apply_payload_v1\",\n strict: true,\n schema: {\n type: \"object\",\n additionalProperties: false,\n properties: {\n job_fields: {\n type: \"object\",\n additionalProperties: false,\n properties: {\n title: { type: \"string\" },\n company: { type: \"string\" },\n location: { type: \"string\" },\n seniority: { type: \"string\" },\n employment_type: { type: \"string\" },\n language: { type: \"string\" },\n requirements: { type: \"array\", items: { type: \"string\" } },\n nice_to_have: { type: \"array\", items: { type: \"string\" } },\n salary_range: { type: \"string\" },\n posted_date: { type: \"string\" },\n recruiter_email: { type: \"string\" }\n },\n required: [\n \"title\",\n \"company\",\n \"location\",\n \"seniority\",\n \"employment_type\",\n \"language\",\n \"requirements\",\n \"nice_to_have\",\n \"salary_range\",\n \"posted_date\",\n \"recruiter_email\"\n ]\n },\n email_subject: { type: \"string\" },\n email_body: { type: \"string\" }\n },\n required: [\"job_fields\", \"email_subject\", \"email_body\"]\n }\n }\n },\n max_output_tokens: 1000\n};\n\nreturn { json: { payload } };"
},
"typeVersion": 2
},
{
"id": "dda0833a-5fbd-46d1-b36b-34833f30042f",
"name": "Call OpenAI API",
"type": "n8n-nodes-base.httpRequest",
"position": [
848,
432
],
"parameters": {
"url": "https://api.openai.com/v1/responses",
"method": "POST",
"options": {},
"jsonBody": "={{ $json.payload }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "openAiApi"
},
"typeVersion": 4.2
},
{
"id": "4cc46606-de4f-402f-beb6-76ea7a433e18",
"name": "Parse AI response",
"type": "n8n-nodes-base.code",
"position": [
1072,
432
],
"parameters": {
"jsCode": "const raw = $json.output?.[0]?.content?.[0]?.text || '{}';\nlet parsed;\ntry { parsed = JSON.parse(raw); }\ncatch (e) { throw new Error(\"Failed to parse model JSON: \" + e.message + \"\\nRaw: \" + raw); }\nreturn { json: parsed };\n// -> json.job_fields.*, json.email_subject, json.email_body"
},
"typeVersion": 2
},
{
"id": "993f7770-de8b-404a-91f7-8da9f28a6e66",
"name": "Store draft in Redis",
"type": "n8n-nodes-base.redis",
"position": [
1296,
432
],
"parameters": {
"key": "={{ $json.job_fields.title.replaceAll(\" \",\"\") }}",
"ttl": 900,
"value": "={{ JSON.stringify({\n Offer: $json.job_fields.title,\n Subject: $json.email_subject,\n Recruiter: $json.job_fields.recruiter_email,\n Email: $json.email_body\n}) }}",
"expire": true,
"keyType": "string",
"operation": "set"
},
"typeVersion": 1
},
{
"id": "1f2c1707-3320-4117-acf3-ffb0a90201b7",
"name": "Send preview to Telegram",
"type": "n8n-nodes-base.telegram",
"position": [
1520,
432
],
"parameters": {
"text": "=\u2728 *Confirm The Job Apply!* \u2728\n\n\ud83d\udcbc *Offer:* {{ $json.job_fields.title }}\n\n\ud83d\udcdd *Subject:* {{ $('Parse AI response').item.json.email_subject }}\n\n\ud83d\udce7 *Recruiter:* {{ $json.job_fields.recruiter_email }}\n\n\ud83d\udc8c *Email Draft:* --------->\n\n\n{{ $('Parse AI response').item.json.email_body }}",
"chatId": "={{ $('Telegram Trigger').item.json.message.chat.id }}",
"replyMarkup": "inlineKeyboard",
"inlineKeyboard": {
"rows": [
{
"row": {
"buttons": [
{
"text": "Send The Email",
"additionalFields": {
"callback_data": "={{ $('Parse AI response').item.json.job_fields.title.replaceAll(\" \",\"\") }}"
}
}
]
}
}
]
},
"additionalFields": {
"parse_mode": "HTML",
"appendAttribution": false
}
},
"typeVersion": 1.2
},
{
"id": "bc21e92c-3f22-4e2c-bc6d-466fe42c624f",
"name": "Retrieve draft from Redis",
"type": "n8n-nodes-base.redis",
"position": [
400,
720
],
"parameters": {
"key": "={{ $json.callback_query.message.reply_markup.inline_keyboard[0][0].callback_data }}",
"options": {},
"operation": "get"
},
"typeVersion": 1
},
{
"id": "6e490fa8-825f-4cf9-bc6d-17009186db01",
"name": "Parse draft data",
"type": "n8n-nodes-base.code",
"position": [
624,
720
],
"parameters": {
"jsCode": "return items.map(i => {\n const p = JSON.parse(i.json.value || i.json.propertyName);\n return { json: { \n offer: p.Offer, subject: p.Subject, recruiter: p.Recruiter, email: p.Email \n }};\n});"
},
"typeVersion": 2
},
{
"id": "687dd808-e352-48ef-b1e6-39752e8df277",
"name": "Download resume from Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
848,
720
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_DRIVE_FILE_ID",
"cachedResultUrl": "YOUR_RESUME_URL",
"cachedResultName": "Your_Resume.pdf"
},
"options": {
"fileName": "Your_Resume.pdf",
"binaryPropertyName": "Resume"
},
"operation": "download"
},
"typeVersion": 3
},
{
"id": "0b57006e-2e50-40dd-b03d-74f7837d89f5",
"name": "Notify immediate send",
"type": "n8n-nodes-base.telegram",
"position": [
1072,
624
],
"parameters": {
"text": "=\u2705 Congratulations, your Email to {{ $json.recruiter }} was sent. Good luck!",
"chatId": "={{ $('Telegram Trigger').item.json.callback_query.message.chat.id }}",
"additionalFields": {}
},
"typeVersion": 1.2
},
{
"id": "1e37adf5-fa37-4433-9996-2f468bf99efb",
"name": "Send email via Gmail",
"type": "n8n-nodes-base.gmail",
"position": [
1744,
816
],
"parameters": {
"sendTo": "={{ $json.recruiter }}",
"message": "={{ $json.email }}",
"options": {},
"subject": "={{ $json.subject }}",
"emailType": "text"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "8f5b6d41-3b35-4033-b8f4-bbfc9e19cdc7",
"name": "Wait before scheduled send",
"type": "n8n-nodes-base.wait",
"position": [
1296,
816
],
"parameters": {
"amount": 10
},
"typeVersion": 1.1
},
{
"id": "c98f6140-35b6-4132-b08f-21a8f25ccfb9",
"name": "Prepare scheduled send",
"type": "n8n-nodes-base.code",
"position": [
1072,
816
],
"parameters": {
"jsCode": "return $input.all();\n"
},
"typeVersion": 2
},
{
"id": "56eb9b23-db15-4f41-9df5-90df54e778ff",
"name": "Download resume for scheduled send",
"type": "n8n-nodes-base.googleDrive",
"position": [
1520,
816
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_DRIVE_FILE_ID",
"cachedResultUrl": "YOUR_RESUME_URL",
"cachedResultName": "Your_Resume.pdf"
},
"options": {
"fileName": "Your_Resume.pdf",
"binaryPropertyName": "Resume"
},
"operation": "download"
},
"typeVersion": 3
},
{
"id": "51ccaa4d-c3dc-4cff-b8df-68ff9d2a7214",
"name": "Update Telegram status",
"type": "n8n-nodes-base.telegram",
"position": [
1296,
624
],
"parameters": {
"text": "=\u2728 *Your Email is scheduled to send in 10s...* \u2728\n\n\ud83d\udcbc *Offer:* {{ $('Parse draft data').item.json.offer }}\n\n\ud83d\udce7 *Recruiter:* {{ $('Parse draft data').item.json.recruiter }}",
"chatId": "={{ $('Telegram Trigger').item.json.callback_query.message.chat.id }}",
"additionalFields": {
"parse_mode": "HTML",
"appendAttribution": false
}
},
"typeVersion": 1.2
},
{
"id": "6c21800e-3510-43e0-9d2d-48767fb61426",
"name": "Notify scheduled send complete",
"type": "n8n-nodes-base.telegram",
"position": [
1968,
816
],
"parameters": {
"text": "=\u2705 Congratulations, your Email to {{ $('Download resume for scheduled send').item.json.recruiter }} was sent. Good luck!",
"chatId": "={{ $('Telegram Trigger').item.json.callback_query.message.chat.id }}",
"additionalFields": {}
},
"typeVersion": 1.2
},
{
"id": "e4a80d39-4ec0-4230-9423-4fc5dc965e09",
"name": "Route by message type",
"type": "n8n-nodes-base.switch",
"position": [
176,
624
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Job_Post_Image",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "fb5bee34-5f87-4e47-8e0d-59b2e5d35c2d",
"operator": {
"type": "array",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.message?.photo }}",
"rightValue": ""
}
]
},
"renameOutput": true
},
{
"outputKey": "Sending",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b65f2e38-c7a3-4e05-ad2c-da10ed0d6a0a",
"operator": {
"type": "object",
"operation": "notEmpty",
"singleValue": true
},
"leftValue": "={{ $json.callback_query?.message }}",
"rightValue": ""
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "4ba5aaeb-8dc2-4ad4-b3ce-808faa8e16ce",
"name": "Show typing indicator",
"type": "n8n-nodes-base.telegram",
"position": [
400,
432
],
"parameters": {
"chatId": "={{ $json.message.chat.id }}",
"operation": "sendChatAction"
},
"typeVersion": 1.2
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "138a7d1b-3417-4739-a4fb-f7dede102804",
"connections": {
"Call OpenAI API": {
"main": [
[
{
"node": "Parse AI response",
"type": "main",
"index": 0
}
]
]
},
"Parse draft data": {
"main": [
[
{
"node": "Download resume from Drive",
"type": "main",
"index": 0
}
]
]
},
"Telegram Trigger": {
"main": [
[
{
"node": "Route by message type",
"type": "main",
"index": 0
}
]
]
},
"Parse AI response": {
"main": [
[
{
"node": "Store draft in Redis",
"type": "main",
"index": 0
}
]
]
},
"Send email via Gmail": {
"main": [
[
{
"node": "Notify scheduled send complete",
"type": "main",
"index": 0
}
]
]
},
"Store draft in Redis": {
"main": [
[
{
"node": "Send preview to Telegram",
"type": "main",
"index": 0
}
]
]
},
"Notify immediate send": {
"main": [
[
{
"node": "Update Telegram status",
"type": "main",
"index": 0
}
]
]
},
"Route by message type": {
"main": [
[
{
"node": "Show typing indicator",
"type": "main",
"index": 0
}
],
[
{
"node": "Retrieve draft from Redis",
"type": "main",
"index": 0
}
]
]
},
"Show typing indicator": {
"main": [
[
{
"node": "Build AI request payload",
"type": "main",
"index": 0
}
]
]
},
"Prepare scheduled send": {
"main": [
[
{
"node": "Wait before scheduled send",
"type": "main",
"index": 0
}
]
]
},
"Build AI request payload": {
"main": [
[
{
"node": "Call OpenAI API",
"type": "main",
"index": 0
}
]
]
},
"Retrieve draft from Redis": {
"main": [
[
{
"node": "Parse draft data",
"type": "main",
"index": 0
}
]
]
},
"Download resume from Drive": {
"main": [
[
{
"node": "Notify immediate send",
"type": "main",
"index": 0
},
{
"node": "Prepare scheduled send",
"type": "main",
"index": 0
}
]
]
},
"Wait before scheduled send": {
"main": [
[
{
"node": "Download resume for scheduled send",
"type": "main",
"index": 0
}
]
]
},
"Download resume for scheduled send": {
"main": [
[
{
"node": "Send email via Gmail",
"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.
gmailOAuth2
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
[](https://www.linkedin.com/in/mosaab-yassir-lafrimi/)[](https://t.me/joevenner)
Source: https://n8n.io/workflows/13077/ — 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.
💥 Automate YouTube thumbnail creation from video links -vide. Uses telegramTrigger, httpRequest, googleDrive, gmail. Event-driven trigger; 25 nodes.
💥 Automate YouTube thumbnail creation from video links -vide. Uses telegramTrigger, httpRequest, googleDrive, gmail. Event-driven trigger; 25 nodes.
Creators, designers, and developers exploring AI-powered image generation. Automation enthusiasts who want to integrate image creation into n8n workflows. Telegram bot builders looking to add visual A
Transcribe audio messages from Telegram using Google Gemini for free.
Send any image description to your Telegram bot and receive a hyper-realistic AI-generated photo back in seconds. A user sends a natural language image request to the Telegram bot The bot confirms the