Skip to content

Instantly share code, notes, and snippets.

@Amitro123
Created November 16, 2025 08:53
Show Gist options
  • Select an option

  • Save Amitro123/e0b0bf1af59deae7af1f632d2e6f6142 to your computer and use it in GitHub Desktop.

Select an option

Save Amitro123/e0b0bf1af59deae7af1f632d2e6f6142 to your computer and use it in GitHub Desktop.
// Paste your API key here
const API_KEY = "";
const DOCUMENT_URL = "";
const DAILY_RUN_HOUR = 8;
const GMAIL_SEARCH_QUERY = "(AI OR machine learning OR agents OR model) is:unread";
function setupDailyTrigger() {
var triggers = ScriptApp.getProjectTriggers();
triggers.forEach(function(trigger) {
if (trigger.getHandlerFunction() === 'analyzeAndSummarizeNewsletters') {
ScriptApp.deleteTrigger(trigger);
}
});
ScriptApp.newTrigger('analyzeAndSummarizeNewsletters')
.timeBased()
.atHour(DAILY_RUN_HOUR)
.everyDays(1)
.create();
Logger.log("✓ Daily trigger set up successfully!");
}
function removeDailyTrigger() {
var triggers = ScriptApp.getProjectTriggers();
triggers.forEach(function(trigger) {
if (trigger.getHandlerFunction() === 'analyzeAndSummarizeNewsletters') {
ScriptApp.deleteTrigger(trigger);
}
});
Logger.log("✓ Daily trigger removed successfully!");
}
function analyzeAndSummarizeNewsletters() {
var doc = DocumentApp.openByUrl(DOCUMENT_URL);
var body = doc.getBody();
if (body.getText() === "") {
body.appendParagraph("AI Newsletter Summaries").setHeading(DocumentApp.ParagraphHeading.TITLE);
}
var threads = GmailApp.search(GMAIL_SEARCH_QUERY);
var summaries = [];
threads.forEach(function(thread) {
try {
var message = thread.getMessages()[thread.getMessageCount() - 1];
var htmlBody = message.getBody();
var plainBody = stripHtmlTags(htmlBody);
if (!plainBody || plainBody.trim().length < 100) {
plainBody = message.getPlainBody();
}
var summary = getGeminiSummary(plainBody, API_KEY);
var links = extractLinks(htmlBody);
var sender = message.getFrom();
var subject = message.getSubject();
var date = message.getDate();
summaries.push({
subject: subject,
sender: sender,
date: date,
summary: summary,
links: links
});
thread.markRead();
} catch (e) {
Logger.log("Error processing email: " + e);
}
});
summaries.forEach(function(item) {
body.appendHorizontalRule();
body.appendParagraph(item.subject).setHeading(DocumentApp.ParagraphHeading.HEADING1);
body.appendParagraph("From: " + item.sender + " | Date: " + item.date.toLocaleString()).setHeading(DocumentApp.ParagraphHeading.HEADING3);
body.appendParagraph("AI Summary:").setHeading(DocumentApp.ParagraphHeading.HEADING2);
body.appendParagraph(item.summary);
if (item.links && item.links !== "No links found") {
body.appendParagraph("Links:").setHeading(DocumentApp.ParagraphHeading.HEADING2);
body.appendParagraph(item.links);
}
});
}
function getGeminiSummary(textBody, apiKey) {
var truncatedBody = textBody.substring(0, 30000);
var prompt = "Summarize this newsletter. Focus on the main topics and key takeaways. Present the summary as 3-5 concise bullet points:\n\n" + truncatedBody;
var models = ["gemini-2.0-flash", "gemini-1.5-flash-latest", "gemini-1.5-flash", "gemini-1.5-pro", "gemini-pro"];
var url = "https://generativelanguage.googleapis.com/v1beta/models/" + models[0] + ":generateContent?key=" + apiKey;
var payload = {
"contents": [{
"parts": [{"text": prompt}]
}]
};
var options = {
'method': 'post',
'contentType': 'application/json',
'payload': JSON.stringify(payload),
'muteHttpExceptions': true
};
try {
var response = UrlFetchApp.fetch(url, options);
if (response.getResponseCode() === 200) {
var jsonResponse = JSON.parse(response.getContentText());
if (jsonResponse.candidates && jsonResponse.candidates[0].content && jsonResponse.candidates[0].content.parts[0]) {
return jsonResponse.candidates[0].content.parts[0].text;
}
} else if (response.getResponseCode() === 429) {
Utilities.sleep(12000);
return getGeminiSummary(textBody, apiKey);
}
return "Error creating summary.";
} catch (e) {
return "Error creating summary.";
}
}
function stripHtmlTags(html) {
var text = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
text = text.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '');
text = text.replace(/<br\s*\/?>/gi, '\n');
text = text.replace(/<\/(p|div|h[1-6]|li)>/gi, '\n');
text = text.replace(/<[^>]+>/g, '');
text = text.replace(/&nbsp;/g, ' ');
text = text.replace(/&lt;/g, '<');
text = text.replace(/&gt;/g, '>');
text = text.replace(/&amp;/g, '&');
text = text.replace(/&quot;/g, '"');
text = text.replace(/&#39;/g, "'");
text = text.replace(/\n\s*\n/g, '\n\n');
return text.trim();
}
function extractLinks(htmlBody) {
var allLinks = [];
var seenUrls = new Set();
var cleanHtml = decodeHtmlEntities(htmlBody);
const linkRegex = /<a\s+href="([^"]+)"[^>]*>([^<]{2,})<\/a>/gi;
var match;
while ((match = linkRegex.exec(cleanHtml)) !== null) {
var url = match[1];
var text = match[2];
var cleanText = text.replace(/(\r\n|\n|\r)/gm, " ").replace(/\s+/g, ' ').trim();
if (cleanText.length > 2 && !cleanText.match(/&#\d+;/)) {
var decodedUrl = tryDecodeBase64Url(url);
if (isRelevantLink(decodedUrl, cleanText)) {
if (!seenUrls.has(decodedUrl)) {
allLinks.push(cleanText + ": " + decodedUrl);
seenUrls.add(decodedUrl);
}
}
}
}
const urlRegex = /(https?:\/\/[^\s<>"{}|\\^`\[\]]{10,})/g;
while ((match = urlRegex.exec(cleanHtml)) !== null) {
var url = match[1];
if (url.match(/&#\d+;/)) continue;
var decodedUrl = tryDecodeBase64Url(url);
if (isRelevantLink(decodedUrl, "")) {
if (!seenUrls.has(decodedUrl)) {
allLinks.push(decodedUrl);
seenUrls.add(decodedUrl);
}
}
}
return allLinks.length > 0 ? allLinks.join('\n') : "No links found";
}
function isRelevantLink(url, text) {
var irrelevantDomains = [
'substack.com/app-link', 'substack.com/redirect', 'substack.com/@', 'substack.com/i/',
'substack-post-media', 'chat.whatsapp.com', 'substack.com/signup', 'substack.com/subscribe',
'substack.com/action/disable_email', 'substack.com/redirect/2/eyJl',
'click.redditmail.com', 'redditstatic', 'redditmail', 'email', 'tracking', 'analytics', 'beacon',
'click.convertkit-mail2.com', 'embed.filekitcdn.com', 'api.filekitcdn.com', 'functions-js.kit.com',
'eotrx.substackcdn.com/open', 'substackcdn.com/image/fetch'
];
var irrelevantTexts = ['Join', 'Subscribe', 'Register', 'Sign up', 'Disable email', 'Unsubscribe'];
var imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.bmp'];
var isIrrelevantDomain = irrelevantDomains.some(domain => url.includes(domain));
var isIrrelevantText = irrelevantTexts.some(t => text.includes(t));
var isImage = imageExtensions.some(ext => url.toLowerCase().endsWith(ext));
return !isIrrelevantDomain && !isIrrelevantText && !isImage;
}
function decodeHtmlEntities(text) {
text = text.replace(/&#(\d+);/g, function(match, dec) {
return String.fromCharCode(parseInt(dec, 10));
});
text = text.replace(/&#x([a-fA-F0-9]+);/g, function(match, hex) {
return String.fromCharCode(parseInt(hex, 16));
});
var entities = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&#39;': "'",
'&nbsp;': ' '
};
for (var entity in entities) {
text = text.replace(new RegExp(entity, 'g'), entities[entity]);
}
return text;
}
function tryDecodeBase64Url(url) {
try {
var base64Matches = url.match(/[A-Za-z0-9+/]{40,}={0,3}/g);
if (base64Matches) {
for (var i = 0; i < base64Matches.length; i++) {
var base64String = base64Matches[i];
try {
var decodedString = Utilities.newBlob(Utilities.base64Decode(base64String)).getDataAsString();
if (decodedString.startsWith('http')) {
return decodedString;
}
} catch (e) {}
}
}
return url;
} catch (e) {
return url;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment