Skip to content

Instantly share code, notes, and snippets.

@FabioFleitas
Last active November 5, 2024 23:51
Show Gist options
  • Select an option

  • Save FabioFleitas/488233bc6f7a822b8693d6eacddf5962 to your computer and use it in GitHub Desktop.

Select an option

Save FabioFleitas/488233bc6f7a822b8693d6eacddf5962 to your computer and use it in GitHub Desktop.
Google Apps Script OpenAI Email Filtering
// Add your OpenAI Key & Org here
var openAIKey = 'sk-';
var openAIOrg = 'org-';
// Modify system & prompt message to meet your email filtering needs
// Here I specifically did it to filter out sales outreach emails
var systemMessage = 'You are a highly intelligent AI trained to identify sales outreach emails. You only respond with Yes or No';
var promptMessage = 'Is this email a sales outreach? Only respond with Yes or No';
// GPT model
var model = 'gpt-4o-mini';
function run() {
// Get the 10 latest Gmail threads that are unread
var threads = GmailApp.getInboxThreads(0, 10);
// Get each label
var aiEmailSpamLabel = GmailApp.getUserLabelByName("AI Email Spam");
var processedByAIEmailFilterLabel = GmailApp.getUserLabelByName("Processed by AI Email Filter");
// Iterate over all of the threads
for (var i = 0; i < threads.length; i++) {
var thread = threads[i];
// If the email thread is already read - do nothing
if (!thread.isUnread()) {
continue;
}
// If we already checked this, then do nothing
if (hasLabel(thread, "Processed by AI Email Filter")) {
continue;
}
// Automatically archive if it already has the AI Email Spam label
if (hasLabel(thread, "AI Email Spam")) {
thread.moveToArchive();
continue;
}
// Get all messages in the thread
var messages = thread.getMessages();
// Get the last message (most recent)
var lastMessage = messages[messages.length - 1];
// Get the sender's email address
var fromAddress = lastMessage.getFrom();
// Check if I have emailed this email before
if (!haveIEmailedThisAddress(fromAddress)) {
var emailBody = lastMessage.getPlainBody()
var isSalesOutreachEmail = classifyEmailWithChatGPT(emailBody)
if (isSalesOutreachEmail) {
// Add "AI Email Spam" Label
thread.addLabel(aiEmailSpamLabel);
// Archive
thread.moveToArchive();
} else {
// Add the "Processed by AI Email Filter" label
thread.addLabel(processedByAIEmailFilterLabel);
}
}
}
}
function hasLabel(thread, labelName) {
// Get all labels attached to the thread
var labels = thread.getLabels();
// Check if any of the labels match the labelName
for (var i = 0; i < labels.length; i++) {
if (labels[i].getName() === labelName) {
return true; // Label found
}
}
return false; // Label not found
}
function extractContentBetweenAngleBrackets(str) {
const regex = /<([^>]*)>/;
const match = str.match(regex);
return match ? match[1] : str;
}
function haveIEmailedThisAddress(emailAddress) {
// Search for emails in the 'Sent' folder that were sent to the specific address
var query = 'to:' + extractContentBetweenAngleBrackets(emailAddress) + ' in:sent';
var threads = GmailApp.search(query);
// If one or more threads are found, it means you've emailed this address
return threads.length > 0;
}
function classifyEmailWithChatGPT(body) {
var apiEndpoint = 'https://api.openai.com/v1/chat/completions';
var prompt = `Body: ${body}\n\n${promptMessage}`;
var payload = {
model: model,
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: prompt }
]
};
var options = {
'method': 'post',
'contentType': 'application/json',
'payload': JSON.stringify(payload),
'headers': {
'Authorization': `Bearer ${openAIKey}`,
'OpenAI-Organization': openAIOrg
}
};
var response = UrlFetchApp.fetch(apiEndpoint, options);
var jsonResponse = JSON.parse(response.getContentText());
// Parse the response to determine if it's a sales outreach
var latestResponse = jsonResponse.choices[0].message.content.trim();
Logger.log(latestResponse);
return latestResponse.toLowerCase().includes("yes"); // Assumes the AI responds with a simple yes or no
}
@theredsix
Copy link

theredsix commented Feb 18, 2024

Is there an ability for me to open a MR to contribute to this repository? Love the idea and this have saved my inbox and I would love to share back a couple of improvements and small edge case fixes.

  1. Strip out email between <> in the getFrom() call so people you've emailed but Gmail does not already have a name for would be matched correctly in haveIEmailedThisAddress()
function extractContentBetweenAngleBrackets(str) {
    const regex = /<([^>]*)>/;
    const match = str.match(regex);
    return match ? match[1] : str;
}

function haveIEmailedThisAddress(emailAddress) {
  // Search for emails in the 'Sent' folder that were sent to the specific address
  var query = 'to:' + extractContentBetweenAngleBrackets(emailAddress) + ' in:sent';
  var threads = GmailApp.search(query);

  // If one or more threads are found, it means you've emailed this address
  return threads.length > 0;
}
  1. Limit the plain email body to 5000 characters to always stay within GPT 3.5 token limits
// Check if I have emailed this email before
    if (!haveIEmailedThisAddress(fromAddress)) {
      var emailBody = lastMessage.getPlainBody().slice(0,5000)

@FabioFleitas
Copy link
Author

@theredsix I added in your first suggestion. That said - it looks like your 2nd suggestion was cut off. Mind posting it again?

@theredsix
Copy link

Updated my original comment!

@FabioFleitas
Copy link
Author

Thanks @theredsix - I went ahead and updated that one as well

@theredsix
Copy link

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment