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