Get Notified When Your Favorite YouTube Channels Post New Videos
Ever find yourself constantly checking YouTube to see if your favorite creators have posted new content? I'll show you how to build a notification system that automatically checks for new videos and sends you beautifully formatted email notifications. This project takes about 30 minutes to set up and requires no advanced coding knowledge.
Here is a screenshot of an email sent by this script notifying me about new videos posted by Lenny's YouTube channel:
Prerequisites
This tutorial assumes you're familiar with:
Working with Google Sheets using Apps Script
Steps to Build Your YouTube Notification SystemStep 1 — Create a Google Sheet to track your favorite YouTube channels
First, let's create a Google Sheet to store our YouTube channel information:
Create a new Google Sheet
Rename the first sheet to "Channels"
Add the following column headers:
Channel Name
Channel ID
Last Notification Sent
To find a channel's ID:
Go to the channel's YouTube page
Right-click and select "View Page Source"
Search for "channelIds"
The channel ID is the text within quotes that is highlighted in yellow:
"channelIds":["UC6t1O76G0jYXOAoYCm153dA"]
Note
The "Last Notification Sent" column will be automatically updated by our script
You don't need to enter anything in this column initially
Step 2 — Set Up the Apps Script Project
Let's create our Apps Script project:
Open the Apps Script editor (Extensions → Apps Script)
Replace the code in the script editor with the code below.
Replace the placeholder
YOUR_EMAIL_ADDRESS
with the email address where you want the script to send notifications.
//@OnlyCurrentDoc
// Email address to send notifications to
const EMAIL_ADDRESS = 'YOUR_EMAIL_ADDRESS'; // Replace with your email
// Main function that checks for new videos
function checkForNewVideos() {
const sheet = SpreadsheetApp.getActive().getSheetByName('Channels');
if (!sheet) {
Logger.log('Error: Could not find sheet named "Channels"');
return;
}
const dataRange = sheet.getDataRange();
const channelData = dataRange.getValues();
// Skip header row
const channels = channelData.slice(1).map(row => ({
channelName: row[0],
channelId: row[1],
lastVideoDate: row[2] || '2000-01-01T00:00:00Z' // Default to old date if no last video
}));
// Collect all new videos across all channels
const newVideosByChannel = [];
channels.forEach((channel, index) => {
const videos = getNewVideosForChannel(channel);
if (videos && videos.length > 0) {
// Update last video date in spreadsheet
const rowIndex = index + 2; // +2 because of 0-based index and header row
sheet.getRange(rowIndex, 3).setValue(videos[0].published); // Column C
newVideosByChannel.push({
channel: channel,
videos: videos
});
}
});
// If we found any new videos, generate and send email
if (newVideosByChannel.length > 0) {
const emailContent = generateEmailContent(newVideosByChannel);
sendNotificationEmail(emailContent);
}
}
function getNewVideosForChannel(channel) {
try {
const feedUrl = `https://www.youtube.com/feeds/videos.xml?channel_id=${channel.channelId}`;
const xmlContent = UrlFetchApp.fetch(feedUrl).getContentText();
const root = XmlService.parse(xmlContent).getRootElement();
const atomNs = XmlService.getNamespace('http://www.w3.org/2005/Atom');
const mediaNs = XmlService.getNamespace('http://search.yahoo.com/mrss/');
const lastNotificationDate = new Date(channel.lastVideoDate);
return root.getChildren('entry', atomNs)
.map(entry => {
const mediaGroup = entry.getChild('group', mediaNs);
const thumbnailUrl = mediaGroup?.getChild('thumbnail', mediaNs)?.getAttribute('url')?.getValue() || '';
return {
title: entry.getChild('title', atomNs)?.getText() || '',
link: entry.getChild('link', atomNs)?.getAttribute('href')?.getValue() || '',
published: entry.getChild('published', atomNs)?.getText() || '',
thumbnail: thumbnailUrl
};
})
.filter(video => video.title && video.published && new Date(video.published) > lastNotificationDate)
.slice(0, 3);
} catch (error) {
Logger.log(`Error fetching videos for channel ${channel.channelName}: ${error}\n${error.stack}`);
return [];
}
}
// Function to generate the complete HTML email content
function generateEmailContent(newVideosByChannel) {
const styles = {
container: 'font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;',
mainTitle: 'color: #333; margin-bottom: 20px;',
channelSection: 'margin-bottom: 30px;',
channelTitle: 'color: #333; margin-bottom: 15px;',
videoCard: 'margin-bottom: 20px; border: 1px solid #ddd; padding: 15px; border-radius: 5px;',
videoContainer: 'display: flex; align-items: start;',
thumbnail: 'width: 120px; height: 90px; margin-right: 15px; border-radius: 3px;',
videoTitle: 'color: #167ac6; text-decoration: none; font-weight: bold; font-size: 16px;',
publishDate: 'color: #666; margin: 5px 0; font-size: 14px;'
};
let html = `
<div style="${styles.container}">
<h1 style="${styles.mainTitle}">New YouTube Videos</h1>
`;
newVideosByChannel.forEach(({ channel, videos }) => {
html += `
<div style="${styles.channelSection}">
<h2 style="${styles.channelTitle}">${channel.channelName}</h2>
`;
videos.forEach(video => {
html += `
<div style="${styles.videoCard}">
<div style="${styles.videoContainer}">
${video.thumbnail ? `
<img src="${video.thumbnail}"
alt="Video thumbnail"
style="${styles.thumbnail}"
/>
` : ''}
<div>
<a href="${video.link}"
style="${styles.videoTitle}">
${video.title}
</a>
<p style="${styles.publishDate}">
Published: ${new Date(video.published).toLocaleString()}
</p>
</div>
</div>
</div>
`;
});
html += `</div>`;
});
html += `</div>`;
return html;
}
// Function to send the notification email
function sendNotificationEmail(htmlContent) {
const subject = 'New Videos From Your Favorite YouTube Channels 📺';
MailApp.sendEmail({
to: EMAIL_ADDRESS,
subject: subject,
htmlBody: htmlContent
});
}
// Function to set up time-based trigger to run every 4 hours
function createTrigger() {
// Delete any existing triggers first to avoid duplicates
const triggers = ScriptApp.getProjectTriggers();
triggers.forEach(trigger => ScriptApp.deleteTrigger(trigger));
// Create new trigger to run at 7am every day
ScriptApp.newTrigger('checkForNewVideos')
.timeBased()
.atHour(7)
.everyDays(1)
.create();
}
Step 3 — Test the Script Manually
Before setting up the automatic trigger, let's test our script to ensure everything works as expected:
First, add at least two YouTube channels to your spreadsheet:
Choose channels that post frequently to increase the chances of finding new videos
Make sure to use valid channel IDs
Open the script editor and click the dropdown menu next to "Debug" to select the
checkForNewVideos
function.Click the "Run" button to execute the script.
When prompted, review and accept the permissions:
The script needs permission to:
Read and write to your Google Sheets
Send emails on your behalf
Make external requests (to fetch YouTube RSS feeds)
Run when you're not present to automatically check for new videos every morning around 7 am.
Check your email for the notification:
Verify that the email formatting looks correct
Confirm that video thumbnails are displaying
Test the video links to ensure they work
Debugging Tips
If you don't receive an email, check your spam folder
Verify that you've replaced
YOUR_EMAIL_ADDRESS
with your actual emailEnsure your channel IDs are correct
Look for any error messages in the execution log
If everything works correctly in your manual test, move on to setting up the automatic trigger. If you encounter any issues, review the execution logs and make necessary adjustments before proceeding.
Step 4 — Set Up the Time Trigger
Now that you've confirmed the script works correctly, let's automate it to run every morning:
Run the
createTrigger()
function:Select
createTrigger
from the function dropdown in the script editorClick the "Run" button
Verify the trigger setup:
Click on "Triggers" in the left sidebar of the Apps Script editor
You should see a new trigger listed with these properties:
Function:
checkForNewVideos
Event source: Time-driven
Trigger Management
The createTrigger()
function automatically removes any existing triggers before creating a new one. This prevents duplicate notifications if you run the function multiple times.
💡 Pro Tip
During testing, you may want to temporarily adjust the trigger to run more frequently (e.g., hourly). Remember to reset it to once a day for regular use to avoid receiving multiple emails daily.
Conclusion
You now have an automated system that keeps you updated about new videos from your favorite YouTube channels. The script runs every morning, checks for new content, and sends you beautifully formatted email notifications.
Thanks for reading!