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:

Screenshot of an email

Prerequisites

This tutorial assumes you're familiar with:

Steps to Build Your YouTube Notification System

Step 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

Screenshot of a Google Sheets spreadsheet containing a list of YouTube channels to monitor for new videos.

To find a channel's ID:

  • Go to the channel's YouTube page

  • Right-click and select "View Page Source"

  • Search for "channelIds"

Screenshot of HTML code obtained by viewing a YouTube channel page's source.

The channel ID is the text within quotes that is highlighted in yellow:

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

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 editor

  • Click 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

Screenshot of the triggers page in Google Apps Script.

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!

Sign up to be notified when I publish new content

By signing up you agree to the Privacy Policy & Terms.