Today I came across an awesome script by @FabioFleitas called AI Email Filtering.js which I used to declutter my Gmail inbox.
I modified the original script to better meet my initial requirements. So how does the script work?
This script starts by analyzing my 10 most recent emails. It checks if the sender is new and if the email has already been labeled. If unlabeled, it uses OpenAI's GPT API to figure out if the email is spam, marketing, or recurring. Based on the response, it either labels and archives the email as "AI: Likely Spam" or marks it as "AI: Reviewed" to prevent unnecessary API calls and costs the next time the inbox is checked.
Before setting up the script, I created a new API key in OpenAI's dashboard and saved my Organization ID for later since it's required to make API calls.
Click on "Create a new label" on the left of your Gmail client
and then...
That covers the setup! With the script in place, my inbox is significantly cleaner, and the best part is, it only costs a dollar or two per month to run.
/* global Logger, UrlFetchApp, GmailApp */
const OPEN_AI_KEY = 'sk-xxxxxxx'
const OPEN_AI_ORG = 'org-xxxxx'
const OPEN_AI_MODEL = 'gpt-3.5-turbo-0125'
const systemMessage =
'You are a highly intelligent AI trained to identify if the email is sales outreach, marketing, or a recurring email like newsletters. You only respond with Yes or No'
const promptMessage = `Is this email a sales, marketing or recurring email?
# Criteria:
- Sales: Product/service offers with action calls.
- Marketing: Promotions, events, updates.
- Recurring: Newsletters, digests with curated content or updates.
Only respond with Yes or No`
const spamLabel = 'AI: Likely Spam'
const reviewedLabel = 'AI: Reviewed'
const hasLabel = (thread, labelName) =>
thread.getLabels().some((label) => label.getName() === labelName)
const haveIEmailedThisAddress = (emailAddress) => {
const query = `to:${emailAddress} in:sent`
return GmailApp.search(query).length > 0
}
const createPrompt = (text) => {
// Remove long links and HTML tags
const content = text
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
.replace(/<a\b[^<]*(?:(?!<\/a>)<[^<]*)*<\/a>/gi, '')
.replace(/<img\b[^<]*>/gi, '')
.replace(/ /gi, ' ')
.replace(/&/gi, '&')
.replace(/</gi, '<')
.replace(/>/gi, '>')
.replace(/<\/?[^>]+>/gi, '')
.replace(/\s{2,}/g, ' ')
.replace(/[\r\n]+/gm, '\n')
.replace(/&(?:[a-z\d]+|#\d+|#x[a-f\d]+);/gi, '')
.slice(0, 2000)
return `Body: ${content}
${promptMessage}`
}
const classifyEmailWithChatGPT = (body) => {
const apiEndpoint = 'https://api.openai.com/v1/chat/completions'
const payload = {
model: OPEN_AI_MODEL,
messages: [
{ role: 'system', content: systemMessage },
{ role: 'user', content: createPrompt(body) },
],
temperature: 0.5,
max_tokens: 4,
top_p: 0.5,
}
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
headers: {
Authorization: `Bearer ${OPEN_AI_KEY}`,
'OpenAI-Organization': OPEN_AI_ORG,
},
}
const response = UrlFetchApp.fetch(apiEndpoint, options)
const jsonResponse = JSON.parse(response.getContentText())
const latestResponse = jsonResponse.choices[0].message.content.trim()
return latestResponse.toLowerCase().includes('yes')
}
const processThread = (
thread,
aiEmailSpamLabel,
processedByAIEmailFilterLabel,
) => {
const lastMessage = thread.getMessages().pop()
const fromAddress = lastMessage.getFrom()
if (!haveIEmailedThisAddress(fromAddress)) {
const emailBody = lastMessage.getPlainBody()
const isSpamEmail = classifyEmailWithChatGPT(emailBody)
if (isSpamEmail) {
thread.addLabel(aiEmailSpamLabel)
thread.moveToArchive()
} else {
thread.addLabel(processedByAIEmailFilterLabel)
}
}
}
const run = () => {
const threads = GmailApp.getInboxThreads(0, 10)
const aiEmailSpamLabel = GmailApp.getUserLabelByName(spamLabel)
const processedByAIEmailFilterLabel =
GmailApp.getUserLabelByName(reviewedLabel)
threads.forEach((thread) => {
if (thread.isUnread() && !hasLabel(thread, reviewedLabel)) {
if (hasLabel(thread, spamLabel)) {
thread.moveToArchive()
return
}
processThread(thread, aiEmailSpamLabel, processedByAIEmailFilterLabel)
}
})
}