

How to Broadcast Gmail Project Updates to Slack with n8n
Polls Gmail for labeled project emails and posts the sender, subject, and a trimmed body excerpt to the matching Slack channel automatically.
Steps and UI details are based on platform versions at time of writing — check each platform for the latest interface.
Best for
Distributed project teams who need external emails — vendor replies, client feedback, stakeholder updates — visible in Slack without anyone forwarding manually.
Not ideal for
Teams receiving 200+ project emails per day; at that volume, digest batching in Make is cheaper and less noisy.
Sync type
scheduledUse case type
notificationReal-World Example
A 22-person product agency manages six active client projects. Client emails land in a shared Gmail inbox and get manually forwarded to Slack by whoever notices them first — which often means a 3-6 hour lag. With this workflow, any email tagged with a project label (e.g. 'Project-Orion') posts to #proj-orion within 5 minutes of arrival, including the sender name, subject line, and first 300 characters of the body. The team stopped missing vendor deadline changes within the first week.
What Will This Cost?
Drag the slider to your expected monthly volume.
Each platform counts differently — Zapier: 1 task per trigger. Make: 1 operation per module per record. n8n: 1 execution per run.





Prices shown for annual billing. Based on published pricing as of April 2026.
Estimated ROI
1000
min saved/mo
$583
labor value/mo
Free
no platform cost
Based on ~2 min manual effort per operation at $35/hr fully loaded labor cost.
Implementation
Import this workflow directly into n8n
Copy the pre-built n8n blueprint and paste it straight into n8n. All modules, filters, and field mappings are already configured — you just need to connect your accounts.
Before You Start
Make sure you have everything ready.
Field Mapping
Map these fields between your apps.
| Field | API Name | |
|---|---|---|
| Required | ||
| Email Message ID | ||
| Subject Line | ||
| Sender Email and Name | ||
| Email Body (trimmed) | ||
| Label IDs | ||
| Slack Channel ID | ||
2 optional fields▸ show
| Received Timestamp | |
| Email Thread ID |
Step-by-Step Setup
Gmail > Settings > Labels > Create new label
Create Gmail Labels for Each Project
Before building anything in n8n, set up one Gmail label per project (e.g. 'Project-Orion', 'Project-Atlas'). Open Gmail, click the gear icon top-right, go to 'See all settings', then the 'Labels' tab. Scroll to the bottom and click 'Create new label'. You'll apply these labels manually or via Gmail filters — the n8n workflow reads them to decide which Slack channel to post to.
- 1Open Gmail and click the gear icon (top-right)
- 2Click 'See all settings'
- 3Select the 'Labels' tab
- 4Scroll to the bottom and click 'Create new label'
- 5Type the label name (e.g. 'Project-Orion') and click 'Create'
n8n Dashboard > Workflows > + New workflow
Create a New Workflow in n8n
Log into your n8n instance (cloud at app.n8n.io or self-hosted). Click the '+ New workflow' button in the top-left of the Workflows dashboard. Give it a clear name like 'Gmail → Slack Project Broadcasting'. You'll land on the canvas with an empty workflow ready for nodes.
- 1Log into your n8n instance
- 2Click '+ New workflow' in the top-left
- 3Click the workflow name at the top and rename it to 'Gmail → Slack Project Broadcasting'
- 4Click anywhere on the canvas to deselect
Canvas > + > Gmail > Gmail Trigger
Add the Gmail Trigger Node
Click the '+' button in the center of the canvas to open the node panel. Search for 'Gmail' and select it. Choose 'Gmail Trigger' as the node type. This node polls Gmail on a schedule — you'll configure it to check every 5 minutes. Set the 'Polling Times' field to '*/5 * * * *' (every 5 minutes). Under 'Filters', set 'Label' to the first project label you created (e.g. 'Project-Orion').
- 1Click the '+' button on the canvas
- 2Search for 'Gmail' in the node search box
- 3Select 'Gmail Trigger'
- 4In the 'Polling Times' field, enter '*/5 * * * *'
- 5Under 'Filters > Label', type your project label name exactly as created in Gmail
Gmail Trigger Node > Credential for Gmail API > Create new credential
Connect Gmail Credentials
Click 'Credential for Gmail API' inside the trigger node and select 'Create new credential'. n8n will prompt you to authenticate via OAuth2. Click 'Sign in with Google', choose the Google account that owns the Gmail inbox, and grant the requested scopes (read mail, modify mail). After authentication, the credential name appears in the dropdown.
- 1Click the 'Credential for Gmail API' dropdown
- 2Select 'Create new credential'
- 3Click 'Sign in with Google'
- 4Choose the correct Google account
- 5Click 'Allow' on the permissions screen
Canvas > + > Code
Add a Code Node to Parse and Trim the Email Body
Click the '+' after the Gmail Trigger node and add a 'Code' node. This node strips HTML from the email body and trims it to 300 characters — raw Gmail API bodies include encoded HTML that looks broken in Slack. Paste the JavaScript below into the code editor. Set the 'Mode' dropdown to 'Run Once for Each Item'.
- 1Click '+' after the Gmail Trigger node
- 2Search for 'Code' and select it
- 3Set 'Mode' to 'Run Once for Each Item'
- 4Paste the transformation code into the editor (see pro_tip_code below)
- 5Click 'Execute node' to test with live Gmail data
Paste this into the Code node (Step 5) set to 'Run Once for Each Item' mode. It decodes the base64 Gmail body, strips HTML, trims to 300 chars, resolves the label ID to a Slack channel ID, and builds the Gmail deep link — all in one pass so you don't need separate transformation nodes.
JavaScript — Code Node// n8n Code Node — Gmail body parser + label-to-channel resolver▸ Show code
// n8n Code Node — Gmail body parser + label-to-channel resolver // Mode: Run Once for Each Item // Place after Gmail Trigger, before Switch node
... expand to see full code
// n8n Code Node — Gmail body parser + label-to-channel resolver
// Mode: Run Once for Each Item
// Place after Gmail Trigger, before Switch node
const item = $input.item.json;
// --- 1. Label ID → Slack Channel ID lookup map ---
// Replace these with your actual Gmail label IDs and Slack channel IDs
const labelChannelMap = {
'Label_3872910456': 'C04ORION123', // Project-Orion → #proj-orion
'Label_4921037281': 'C04ATLAS456', // Project-Atlas → #proj-atlas
'Label_5034829174': 'C04HERMES789', // Project-Hermes → #proj-hermes
};
// --- 2. Resolve Slack channel from label IDs ---
const labelIds = item.labelIds || [];
let slackChannelId = null;
for (const labelId of labelIds) {
if (labelChannelMap[labelId]) {
slackChannelId = labelChannelMap[labelId];
break;
}
}
// --- 3. Extract and decode email body ---
// Gmail API returns body as base64url-encoded string
let rawBody = '';
try {
// Try text/plain part first
const parts = item.payload?.parts || [];
const plainPart = parts.find(p => p.mimeType === 'text/plain');
const htmlPart = parts.find(p => p.mimeType === 'text/html');
const bodyData = plainPart?.body?.data
|| htmlPart?.body?.data
|| item.payload?.body?.data
|| '';
if (bodyData) {
// Decode base64url to string
rawBody = Buffer.from(bodyData.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf-8');
} else {
// Fallback: use snippet from Gmail trigger
rawBody = item.snippet || '';
}
} catch (e) {
rawBody = item.snippet || '[Body unavailable]';
}
// --- 4. Strip HTML tags if body is HTML ---
const cleanBody = rawBody
.replace(/<style[\s\S]*?<\/style>/gi, '')
.replace(/<script[\s\S]*?<\/script>/gi, '')
.replace(/<[^>]+>/g, ' ')
.replace(/ /g, ' ')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/\s+/g, ' ')
.trim()
.substring(0, 300);
const bodyExcerpt = cleanBody.length === 300 ? cleanBody + '...' : cleanBody;
// --- 5. Format received timestamp ---
const receivedDate = item.internalDate
? new Date(parseInt(item.internalDate)).toLocaleString('en-US', {
month: 'short', day: 'numeric', year: 'numeric',
hour: 'numeric', minute: '2-digit', hour12: true
})
: 'Unknown time';
// --- 6. Build Gmail deep link ---
const gmailLink = `https://mail.google.com/mail/u/0/#inbox/${item.id}`;
// --- 7. Build Slack message text ---
const slackText = [
`*New email: ${item.subject || '(no subject)'}*`,
`From: ${item.from || 'Unknown sender'}`,
`Received: ${receivedDate}`,
'',
bodyExcerpt,
'',
`<${gmailLink}|Open in Gmail>`
].join('\n');
// --- 8. Return enriched item ---
return {
json: {
...item,
slackChannelId,
cleanBody: bodyExcerpt,
receivedDate,
gmailLink,
slackText,
routingResolved: slackChannelId !== null,
}
};Canvas > + > Switch
Add a Switch Node to Route by Project Label
Click '+' after the Code node and add a 'Switch' node. This routes each email to the correct Slack channel based on its Gmail label. Set 'Mode' to 'Rules'. Add one rule per project: set the condition to 'String > Contains' and check '{{ $json.labelIds }}' against the label name (e.g. 'Project-Orion'). Each rule gets its own output branch.
- 1Click '+' after the Code node
- 2Search for 'Switch' and select it
- 3Set 'Mode' to 'Rules'
- 4Click 'Add Rule'
- 5Set condition: Value 1 = '{{ $json.labelIds.join(",") }}', Operation = 'Contains', Value 2 = 'Project-Orion'
- 6Repeat for each additional project label
Canvas > + > Code (after Switch, on each branch)
Resolve Label IDs to Readable Channel Names
After the Switch node, add another Code node on each branch (or one before the Switch) to map Gmail label IDs to Slack channel IDs. Gmail's API returns label IDs like 'Label_1234567890' rather than 'Project-Orion'. Build a simple lookup object in the Code node that maps each label ID to the corresponding Slack channel ID. You get the Slack channel ID by right-clicking a channel in Slack and selecting 'Copy link' — the ID is the last segment after the final slash.
- 1Right-click each Slack channel and copy its link to get the channel ID
- 2Click '+' before or after the Switch node
- 3Add a Code node in 'Run Once for Each Item' mode
- 4Paste the label-to-channel lookup map (included in pro_tip_code)
- 5Verify the output shows a 'slackChannelId' field with the correct channel ID
Paste this into the Code node (Step 5) set to 'Run Once for Each Item' mode. It decodes the base64 Gmail body, strips HTML, trims to 300 chars, resolves the label ID to a Slack channel ID, and builds the Gmail deep link — all in one pass so you don't need separate transformation nodes.
JavaScript — Code Node// n8n Code Node — Gmail body parser + label-to-channel resolver▸ Show code
// n8n Code Node — Gmail body parser + label-to-channel resolver // Mode: Run Once for Each Item // Place after Gmail Trigger, before Switch node
... expand to see full code
// n8n Code Node — Gmail body parser + label-to-channel resolver
// Mode: Run Once for Each Item
// Place after Gmail Trigger, before Switch node
const item = $input.item.json;
// --- 1. Label ID → Slack Channel ID lookup map ---
// Replace these with your actual Gmail label IDs and Slack channel IDs
const labelChannelMap = {
'Label_3872910456': 'C04ORION123', // Project-Orion → #proj-orion
'Label_4921037281': 'C04ATLAS456', // Project-Atlas → #proj-atlas
'Label_5034829174': 'C04HERMES789', // Project-Hermes → #proj-hermes
};
// --- 2. Resolve Slack channel from label IDs ---
const labelIds = item.labelIds || [];
let slackChannelId = null;
for (const labelId of labelIds) {
if (labelChannelMap[labelId]) {
slackChannelId = labelChannelMap[labelId];
break;
}
}
// --- 3. Extract and decode email body ---
// Gmail API returns body as base64url-encoded string
let rawBody = '';
try {
// Try text/plain part first
const parts = item.payload?.parts || [];
const plainPart = parts.find(p => p.mimeType === 'text/plain');
const htmlPart = parts.find(p => p.mimeType === 'text/html');
const bodyData = plainPart?.body?.data
|| htmlPart?.body?.data
|| item.payload?.body?.data
|| '';
if (bodyData) {
// Decode base64url to string
rawBody = Buffer.from(bodyData.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf-8');
} else {
// Fallback: use snippet from Gmail trigger
rawBody = item.snippet || '';
}
} catch (e) {
rawBody = item.snippet || '[Body unavailable]';
}
// --- 4. Strip HTML tags if body is HTML ---
const cleanBody = rawBody
.replace(/<style[\s\S]*?<\/style>/gi, '')
.replace(/<script[\s\S]*?<\/script>/gi, '')
.replace(/<[^>]+>/g, ' ')
.replace(/ /g, ' ')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/\s+/g, ' ')
.trim()
.substring(0, 300);
const bodyExcerpt = cleanBody.length === 300 ? cleanBody + '...' : cleanBody;
// --- 5. Format received timestamp ---
const receivedDate = item.internalDate
? new Date(parseInt(item.internalDate)).toLocaleString('en-US', {
month: 'short', day: 'numeric', year: 'numeric',
hour: 'numeric', minute: '2-digit', hour12: true
})
: 'Unknown time';
// --- 6. Build Gmail deep link ---
const gmailLink = `https://mail.google.com/mail/u/0/#inbox/${item.id}`;
// --- 7. Build Slack message text ---
const slackText = [
`*New email: ${item.subject || '(no subject)'}*`,
`From: ${item.from || 'Unknown sender'}`,
`Received: ${receivedDate}`,
'',
bodyExcerpt,
'',
`<${gmailLink}|Open in Gmail>`
].join('\n');
// --- 8. Return enriched item ---
return {
json: {
...item,
slackChannelId,
cleanBody: bodyExcerpt,
receivedDate,
gmailLink,
slackText,
routingResolved: slackChannelId !== null,
}
};Canvas > + > Slack > Message > Post
Add the Slack Node to Post the Message
Click '+' at the end of each Switch branch and add a 'Slack' node. Set 'Resource' to 'Message' and 'Operation' to 'Post'. Set 'Channel' to '{{ $json.slackChannelId }}'. In the 'Text' field, build the message using the parsed fields from earlier nodes. A good format: '*New email: {{ $json.subject }}*\nFrom: {{ $json.from }}\n{{ $json.cleanBody }}'.
- 1Click '+' after each Switch branch
- 2Search for 'Slack' and select 'Slack' node
- 3Set 'Resource' to 'Message', 'Operation' to 'Post'
- 4Set 'Channel' to '{{ $json.slackChannelId }}'
- 5Paste the message template into the 'Text' field
channel: {{channel}}
ts: {{ts}}
Slack Node > Credential for Slack API > Create new credential
Connect Slack Credentials
Inside the Slack node, click 'Credential for Slack API' and select 'Create new credential'. Choose 'OAuth2' and click 'Connect my account'. You'll be redirected to Slack to install the n8n app into your workspace. Grant the required scopes: 'chat:write' and 'channels:read'. After approval, the credential appears and the node shows no error.
- 1Click 'Credential for Slack API' dropdown
- 2Select 'Create new credential'
- 3Click 'Connect my account'
- 4Select your Slack workspace
- 5Click 'Allow' to grant chat:write and channels:read scopes
Canvas > + > Gmail > Message > Modify
Mark Processed Emails to Prevent Duplicates
Add a second Gmail node after the Slack node. Set 'Resource' to 'Message' and 'Operation' to 'Modify'. Pass in '{{ $json.id }}' as the message ID. Under 'Add Labels', add a label called 'n8n-processed' (create this in Gmail first). This prevents the polling trigger from picking up the same email on the next 5-minute cycle.
- 1Create an 'n8n-processed' label in Gmail (Settings > Labels > Create new label)
- 2Click '+' after the Slack node
- 3Search for 'Gmail' and select it
- 4Set 'Resource' to 'Message', 'Operation' to 'Modify'
- 5Set 'Message ID' to '{{ $json.id }}'
- 6Under 'Add Labels', select 'n8n-processed'
n8n Canvas > Save > Active toggle > Executions tab
Activate and Monitor the Workflow
Click 'Save' in the top-right, then toggle the workflow from 'Inactive' to 'Active' using the switch at the top of the canvas. Send a test email to the Gmail inbox with the correct label applied and wait up to 5 minutes for the poll cycle to fire. Check the 'Executions' tab in n8n to see run history, input/output data per node, and any errors. Set up n8n's built-in error workflow to alert you if the Gmail or Slack nodes fail.
- 1Click 'Save' (top-right)
- 2Toggle the workflow switch from 'Inactive' to 'Active'
- 3Send a test email to Gmail with the project label applied
- 4Wait up to 5 minutes, then check the 'Executions' tab
- 5Verify the test message appears in the correct Slack channel
Going live
Production Checklist
Before you turn this on for real, confirm each item.
Troubleshooting
Common errors and how to fix them.
Frequently Asked Questions
Common questions about this workflow.
Analysis
Use n8n for this if your team self-hosts or has strict data residency requirements. Project emails from clients and vendors often contain sensitive commercial terms — you may not want that content passing through Zapier's or Make's cloud infrastructure. n8n lets you run this entire workflow on your own server, and the Code node gives you real body parsing (stripping HTML, decoding base64) that Zapier's Formatter can't match without multiple steps. The one scenario where you'd pick something else: if your team has no one comfortable editing a JavaScript snippet, Make's Gmail-to-Slack scenario does the same routing with a visual text parser and no code required.
The cost math here is straightforward. n8n cloud charges by workflow executions. This workflow runs 3 nodes per email (Gmail Trigger fires, Code runs, Slack posts) plus one cleanup node — call it 4 executions per email processed. At 50 project emails per day, that's 200 executions/day, roughly 6,000/month. n8n's Starter plan includes 2,500 executions/month for $20; Pro gives you 10,000 for $50. At 50 emails/day you're on Pro. Zapier at the same volume costs $49/month (Professional, 2,000 tasks — you'd blow past it fast since Zapier counts each action separately). Make's Core plan gives you 10,000 operations for $9/month — the same workflow in Make costs about 4 operations per email, so 50 emails/day = 6,000 ops/month, well inside Make's Core tier. Make wins on price here by $41/month.
Make's Gmail watch trigger is marginally more reliable for this use case — it uses push notifications via Gmail's watch API rather than polling, so emails hit Slack in under 60 seconds instead of up to 5 minutes. Zapier's Gmail trigger is similarly near-real-time on paid plans and has a cleaner label filter UI, but its text formatting step requires a separate Formatter action that costs an extra task. Power Automate's Office 365 version of this workflow is excellent if your org runs Exchange, but its Gmail connector is throttled and often laggy — avoid it for Gmail specifically. Pipedream handles this well with its native Gmail source that uses push notifications, and the code step is cleaner to write than n8n's for developers. n8n is still the right call if you want self-hosting, the most control over body parsing, and don't mind the 5-minute polling delay.
Three things you'll hit after launch. First: Gmail's base64url encoding. The API doesn't return plain text — it returns base64url-encoded body data. If you skip the decoding step and just use the 'snippet' field, you get 100 characters of plain text with no HTML, which looks clean in testing but loses context in production. Decode the full body. Second: Slack's rate limit is 1 message per second per channel. If a batch of emails arrives simultaneously (common after a weekend), n8n will try to post several times per second and you'll get 429 errors. Add a 'Wait' node set to 1 second between each Slack post if you're processing more than 5 emails per execution cycle. Third: Google's OAuth token refresh silently fails when your Cloud project is in Testing mode — the workflow just stops executing with no error email to you. This happens to everyone who sets up Gmail OAuth for the first time. Publish the app to Production in Google Cloud Console immediately after testing.
Ideas for what to build next
- →Add Attachment Detection and Alerts — Extend the Code node to check 'payload.parts' for attachment MIME types. If an attachment exists, append a warning line to the Slack message like '📎 1 attachment — open in Gmail to download'. Teams miss attached contracts and specs when only the body excerpt posts.
- →Build a Daily Digest Instead of Per-Email Posts — Replace the 5-minute polling trigger with a scheduled trigger at 9am and 3pm. Batch all unprocessed project emails since the last run into a single Slack message per channel, formatted as a numbered list. This cuts Slack noise by 80% for high-volume projects.
- →Log All Broadcasts to a Google Sheet — Add a Google Sheets node after the Slack post that appends a row with: timestamp, sender, subject, project label, Slack channel, and message ID. After 30 days you'll have a full audit trail of external communications per project — useful for client billing and post-mortems.
Related guides
How to Share Notion Meeting Notes to Slack with Pipedream
~15 min setup
How to Share Notion Meeting Notes to Slack with Power Automate
~15 min setup
How to Share Notion Meeting Notes to Slack with n8n
~20 min setup
How to Send Notion Meeting Notes to Slack with Zapier
~8 min setup
How to Share Notion Meeting Notes to Slack with Make
~12 min setup
How to Create Notion Tasks from Slack with Pipedream
~15 min setup