Skip to content

Commit 8b146db

Browse files
committed
Gmail Spam AI
1 parent 9134c60 commit 8b146db

File tree

6 files changed

+715
-563
lines changed

6 files changed

+715
-563
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ These [Google Apps Script](https://www.labnol.org/topic/google-apps-script) code
1212
| ---------------------------------------------------------------- | ------------------------------------------ |
1313
| [Core Web Vitals](./google-apps-script/core-vitals/) | Measure Core Vitals inside Google Sheets |
1414
| [Disposable Gmail](./google-apps-script/disposable-gmail/) | Use Gmail as a temporary email address |
15+
| [Gmail Spam Classifier](./google-apps-script/gmail-spam/) | Detect false positives in Gmail spam |
1516
| [Google Drive Lock](./google-apps-script/drive-lock/) | Lock Google Drive files with Apps Script |
1617
| [Factory Reset Gmail](./google-apps-script/factory-reset-gmail/) | Delete everything in your Gmail account |
1718
| [Google Drive Monitor](./google-apps-script/google-drive-watch/) | Monitor deleted files in your Google Drive |
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Gmail Spam AI 🐢
2+
3+
[Find Legitimate Emails in Gmail Spam with AI](https://www.labnol.org/internet/email/prevent-gmail-from-marking-spam-emails/4030/)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"timeZone": "Asia/Kolkata",
3+
"dependencies": {},
4+
"exceptionLogging": "STACKDRIVER",
5+
"runtimeVersion": "V8",
6+
"oauthScopes": [
7+
"https://mail.google.com/",
8+
"https://www.googleapis.com/auth/script.external_request",
9+
"https://www.googleapis.com/auth/script.scriptapp"
10+
]
11+
}

google-apps-script/gmail-spam/code.js

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* Gmail Spam AI Classifier
3+
4+
* Web: https://digitalinspiration.com/
5+
* MIT License
6+
* */
7+
8+
// Google Script to analyze spam emails in Gmail using OpenAI and
9+
// send a report of false positives that could be legitimate emails.
10+
11+
// Basic configuration
12+
const USER_EMAIL = '[email protected]'; // Email address to send the report to
13+
const OPENAI_API_KEY = 'sk-proj-123'; // API key for OpenAI
14+
const OPENAI_MODEL = 'gpt-4o'; // Model name to use with OpenAI
15+
const USER_LANGUAGE = 'English'; // Language for the email summary
16+
17+
// Advanced configuration
18+
const HOURS_AGO = 24; // Time frame to search for emails (in hours)
19+
const MAX_THREADS = 25; // Maximum number of email threads to process
20+
const MAX_BODY_LENGTH = 200; // Maximum length of email body to include in the AI prompt
21+
const SPAM_THRESHOLD = 2; // Threshold for spam score to include in the report
22+
23+
const SYSTEM_PROMPT = `You are an AI email classifier. Given the content of an email, analyze it and assign a spam score on a scale from 0 to 10, where 0 indicates a legitimate email and 10 indicates a definite spam email. Provide a short summary of the email in ${USER_LANGUAGE}. Your response should be in JSON format.`;
24+
let tokens = 0; // Variable to track the number of tokens used in the AI API calls
25+
26+
// Find unread emails in Gmail spam folder
27+
const getSpamThreads_ = () => {
28+
const epoch = (date) => Math.floor(date.getTime() / 1000);
29+
const beforeDate = new Date();
30+
const afterDate = new Date();
31+
afterDate.setHours(afterDate.getHours() - HOURS_AGO);
32+
const searchQuery = `is:unread in:spam after:${epoch(afterDate)} before:${epoch(beforeDate)}`;
33+
return GmailApp.search(searchQuery, 0, MAX_THREADS);
34+
};
35+
36+
// Create a prompt for the OpenAI model using the email message
37+
const getMessagePrompt_ = (message) => {
38+
// remove all URLs, and whitespace characters
39+
const body = message
40+
.getPlainBody()
41+
.replace(/https?:\/\/[^\s>]+/g, '')
42+
.replace(/[\n\r\t]/g, ' ')
43+
.replace(/\s+/g, ' ')
44+
.trim();
45+
return [
46+
`Subject: ${message.getSubject()}`,
47+
`Sender: ${message.getFrom()}`,
48+
`Body: ${body.substring(0, MAX_BODY_LENGTH)}`, // Email body (truncated)
49+
].join('\n');
50+
};
51+
52+
// Function to get the spam score and summary from OpenAI
53+
const getMessageScore_ = (messagePrompt) => {
54+
const apiUrl = `https://api.openai.com/v1/chat/completions`;
55+
const headers = {
56+
'Content-Type': 'application/json',
57+
Authorization: `Bearer ${OPENAI_API_KEY}`,
58+
};
59+
const response = UrlFetchApp.fetch(apiUrl, {
60+
method: 'POST',
61+
headers,
62+
payload: JSON.stringify({
63+
model: OPENAI_MODEL,
64+
messages: [
65+
{ role: 'system', content: SYSTEM_PROMPT },
66+
{ role: 'user', content: messagePrompt },
67+
],
68+
temperature: 0.2,
69+
max_tokens: 124,
70+
response_format: { type: 'json_object' },
71+
}),
72+
});
73+
const data = JSON.parse(response.getContentText());
74+
tokens += data.usage.total_tokens;
75+
const content = JSON.parse(data.choices[0].message.content);
76+
return content;
77+
};
78+
79+
// Generate a link to the email message in Gmail
80+
const getMessageLink_ = (message) => {
81+
const from = message.getFrom() || '';
82+
const matches = from.match(/<([^>]+)>/);
83+
const senderEmail = matches ? matches[1] : from;
84+
const messageLink = Utilities.formatString(
85+
'https://mail.google.com/mail/u/0/#search/%s',
86+
encodeURIComponent(`from:${senderEmail} in:spam`)
87+
);
88+
return `<a href=${messageLink}>${senderEmail}</a>`;
89+
};
90+
91+
// Send a report of false positives to the user's email
92+
const reportFalsePositives = () => {
93+
const html = [];
94+
const threads = getSpamThreads_();
95+
for (let i = 0; i < threads.length; i += 1) {
96+
const [message] = threads[i].getMessages(); // Get the first message in each thread
97+
const messagePrompt = getMessagePrompt_(message); // Create the message prompt
98+
const { spam_score, summary } = getMessageScore_(messagePrompt); // Get the spam score and summary from OpenAI
99+
if (spam_score <= SPAM_THRESHOLD) {
100+
// Add email message to the report if the spam score is below the threshold
101+
html.push(`<tr><td>${getMessageLink_(message)}</td> <td>${summary}</td></tr>`);
102+
}
103+
}
104+
threads.forEach((thread) => thread.markRead()); // Mark all processed emails as read
105+
if (html.length > 0) {
106+
const htmlBody = [
107+
`<table border="1" cellpadding="10"
108+
style="max-width: 720px; border: 1px solid #ddd; margin-top: 16px; border-collapse: collapse;">`,
109+
'<tr><th>Email Sender</th><th>Summary</th></tr>',
110+
html.join(''),
111+
'</table>',
112+
].join('');
113+
const subject = `Gmail Spam Report - ${tokens} tokens used`;
114+
GmailApp.sendEmail(USER_EMAIL, subject, '', { htmlBody });
115+
}
116+
};

0 commit comments

Comments
 (0)