Agentero
Connect your own Agent, keep research context in a local Vault. Seamlessly migrate from Zotero, read, understand, link, and reuse your research context.
AI translation
A personal AI agent that lives inside your own Google account. Stars important emails, deletes spam, and unsubscribes you from forgotten newsletters.
ProductivityTypeScript · CSSGitHub @johntperales10-inboxaiOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Build me a clean, modern 3-stage web app called "InboxAI" — a Gmail AI Agent setup wizard. The app lives on one page and walks users through 3 stages. Only one stage is visible at a time.
=== BRAND & STYLE ===
=== STAGE 1 — LANDING / HERO === Full screen hero section with:
=== STAGE 2 — SETTINGS FORM === Header: "Tell us about your inbox" with subtext "Takes 2 minutes. We'll build your custom script with your settings already filled in."
Back button at top left that goes back to Stage 1.
Form fields:
Big button at bottom: "Build my custom script →" — clicking it generates the script and goes to Stage 3.
=== STAGE 3 — GENERATED SCRIPT + INSTALL STEPS === Back button at top left that goes back to Stage 2.
Header: "Your script is ready" Subtext: "Follow the 4 steps below. Takes about 5 minutes. You'll never need to touch code again."
Green success banner: "✓ Your settings have been baked into the script below."
SCRIPT OUTPUT BOX:
INSTALL STEPS — numbered list of cards: Step 1: "Go to script.google.com and sign in with the same Google account as your Gmail. Click New project." Step 2: "Delete everything in the editor. Paste your script above using Ctrl+V (Cmd+V on Mac). Save with Ctrl+S." Step 3: "Click the dropdown at the top of the editor — it says myFunction by default. Change it to testGemini. Press ▶ Run. Accept any permissions Google asks for." Step 4: "Once that works, change the dropdown to setupTrigger and press ▶ Run. Your agent is now live and runs every hour automatically." Step 5: "To stop the agent any time, run stopAgent from the same dropdown. To fully remove access go to myaccount.google.com/permissions."
=== THE SCRIPT TEMPLATE === When the user clicks "Build my custom script", generate this script with their values filled in. Replace all placeholder values with what the user entered in the form:
// ============================================================ // GMAIL AI AGENT — your custom build // Paste into script.google.com → New project → Run setupTrigger // ============================================================
const SETTINGS = { geminiApiKey: "PASTE_YOUR_GEMINI_API_KEY_HERE", trustedSenders: [ [LIST OF EMAILS FROM FORM, each on its own line in quotes] ], deleteSpam: [TRUE OR FALSE FROM TOGGLE], unsubscribeAfterDays: [NUMBER FROM SLIDER], notificationEmail: Session.getActiveUser().getEmail(), useTelegram: [TRUE OR FALSE FROM TOGGLE], telegramBotToken: "[TOKEN FROM FORM OR PASTE_YOUR_BOT_TOKEN_HERE]", telegramChatId: "[CHAT ID FROM FORM OR PASTE_YOUR_CHAT_ID_HERE]", batchSize: 20, lookbackDays: 3 };
const Memory = {
load() { const raw = PropertiesService.getScriptProperties().getProperty("agentMemory"); return raw ? JSON.parse(raw) : {}; },
save(memory) { PropertiesService.getScriptProperties().setProperty("agentMemory", JSON.stringify(memory)); },
remember(email, action, reason) { const m = this.load(); m[email.toLowerCase()] = { action, reason, learnedOn: new Date().toLocaleDateString() }; this.save(m); },
recall(email) { return this.load()[email.toLowerCase()] || null; },
summarize() { const m = this.load(); const e = Object.entries(m); if (!e.length) return "No senders learned yet."; return e.map(([em, d]) => ${em}: ${d.action} — "${d.reason}").join("\n"); },
forget(email) { const m = this.load(); delete m[email.toLowerCase()]; this.save(m); },
reset() { PropertiesService.getScriptProperties().deleteProperty("agentMemory"); }
};
function askGemini(senderEmail, senderName, subject, snippet) {
const prompt = You are a Gmail assistant. Analyze this email and decide what to do.\nFrom: ${senderName} <${senderEmail}>\nSubject: ${subject}\nPreview: ${snippet}\nRules: STAR=personal/important, TRASH=junk/scam, UNSUB=newsletter/marketing, FLAG=unsure\nReply ONLY:\nDECISION: [STAR|TRASH|UNSUB|FLAG]\nREASON: [one sentence];
const url = https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${SETTINGS.geminiApiKey};
try {
const res = UrlFetchApp.fetch(url, { method: "post", contentType: "application/json", payload: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }], generationConfig: { temperature: 0.1, maxOutputTokens: 60 } }), muteHttpExceptions: true });
const text = JSON.parse(res.getContentText())?.candidates?.[0]?.content?.parts?.[0]?.text?.trim();
if (!text) return { action: "FLAG", reason: "No response from Gemini" };
const action = (text.match(/DECISION:\s*(STAR|TRASH|UNSUB|FLAG)/i) || [])[1]?.toUpperCase() || "FLAG";
const reason = (text.match(/REASON:\s*(.+)/i) || [])[1]?.trim() || "No reason";
return { action, reason };
} catch (e) { return { action: "FLAG", reason: "API error: " + e.message }; }
}
function setupTrigger() { ScriptApp.getProjectTriggers().forEach(t => ScriptApp.deleteTrigger(t)); ScriptApp.newTrigger("runAgent").timeBased().everyHours(1).create(); sendNotification("✅ Gmail Agent active!", "Your Gmail AI Agent is running.", "✅ Gmail AI Agent is live!"); Logger.log("Agent activated."); }
function runAgent() {
const flagged = [], actioned = [];
processTrustedSenders(actioned);
if (SETTINGS.deleteSpam) clearSpam(actioned);
const threads = GmailApp.search(is:unread newer_than:${SETTINGS.lookbackDays}d, 0, SETTINGS.batchSize);
for (const thread of threads) {
const msgs = thread.getMessages();
if (!msgs.length) continue;
const msg = msgs[msgs.length - 1];
const senderFull = msg.getFrom();
const senderEmail = extractEmail(senderFull);
if (isTrustedSender(senderEmail)) continue;
let decision = Memory.recall(senderEmail);
if (!decision) { decision = askGemini(senderEmail, extractName(senderFull), msg.getSubject() || "(no subject)", msg.getPlainBody().substring(0, 300)); Memory.remember(senderEmail, decision.action, decision.reason); }
switch (decision.action) {
case "STAR": msg.star(); actioned.push(⭐ Starred: "${msg.getSubject()}"); break;
case "TRASH": thread.moveToTrash(); actioned.push(🗑️ Trashed: "${msg.getSubject()}"); break;
case "UNSUB":
const url = getUnsubscribeUrl(msg);
if (url && attemptUnsubscribe(url)) { thread.moveToTrash(); actioned.push(📧 Unsubscribed: ${senderEmail}); }
else flagged.push({ from: senderFull, subject: msg.getSubject(), reason: "Unsubscribe failed — manual action needed" });
break;
default: flagged.push({ from: senderFull, subject: msg.getSubject(), reason: decision.reason });
}
}
if (flagged.length) sendFlagNotification(flagged, actioned);
}
function processTrustedSenders(actioned) {
GmailApp.search(is:unread newer_than:${SETTINGS.lookbackDays}d, 0, SETTINGS.batchSize).forEach(thread => {
thread.getMessages().forEach(msg => { if (msg.isUnread() && isTrustedSender(extractEmail(msg.getFrom()))) { msg.star(); actioned.push(⭐ Starred (trusted): "${msg.getSubject()}"); } });
});
}
function isTrustedSender(email) { return SETTINGS.trustedSenders.some(t => email.toLowerCase().includes(t.toLowerCase())); }
function clearSpam(actioned) { const s = GmailApp.search("in:spam", 0, SETTINGS.batchSize); if (s.length) { GmailApp.moveThreadsToTrash(s); actioned.push(🗑️ Trashed ${s.length} spam threads); } }
function sendFlagNotification(flagged, actioned) {
const lines = flagged.map((e, i) => ${i + 1}. From: ${e.from}\n "${e.subject}"\n Reason: ${e.reason}).join("\n\n");
sendNotification(🚩 ${flagged.length} email(s) need your attention, ${flagged.length} email(s) were flagged:\n\n${lines}\n\nThese were NOT touched., 🚩 ${flagged.length} email(s) flagged:\n${lines});
}
function sendNotification(subject, emailBody, telegramText) {
try { GmailApp.sendEmail(SETTINGS.notificationEmail, subject, emailBody); } catch (e) {}
if (SETTINGS.useTelegram && SETTINGS.telegramBotToken !== "PASTE_YOUR_BOT_TOKEN_HERE") {
try { UrlFetchApp.fetch(https://api.telegram.org/bot${SETTINGS.telegramBotToken}/sendMessage, { method: "post", contentType: "application/json", payload: JSON.stringify({ chat_id: SETTINGS.telegramChatId, text: telegramText || subject }), muteHttpExceptions: true }); } catch (e) {}
}
}
function extractEmail(f) { const m = f.match(/<(.+?)>/); return m ? m[1].toLowerCase() : f.toLowerCase().trim(); } function extractName(f) { const m = f.match(/^([^<]+)</); return m ? m[1].trim() : f; } function getUnsubscribeUrl(msg) { try { const m = msg.getRawContent().match(/List-Unsubscribe:\s*<(https?://[^>]+)>/i); return m ? m[1] : null; } catch (e) { return null; } } function attemptUnsubscribe(url) { try { const r = UrlFetchApp.fetch(url, { method: "get", followRedirects: true, muteHttpExceptions: true }); return r.getResponseCode() < 400; } catch (e) { return false; } }
function testGemini() { Logger.log(JSON.stringify(askGemini("[email protected]", "Example", "Weekly digest", "Top stories this week..."))); } function testTelegram() { SETTINGS.useTelegram = true; sendNotification("🤖 Test", "Test email.", "🤖 Telegram connected!"); } function showMemory() { Logger.log(Memory.summarize()); } function forgetSender() { Memory.forget("[email protected]"); } function resetMemory() { Memory.reset(); } function stopAgent() { ScriptApp.getProjectTriggers().forEach(t => ScriptApp.deleteTrigger(t)); Logger.log("Agent stopped."); }
=== IMPORTANT NOTES FOR THE BUILDER ===
This project was built with Lovable.
Live app: https://inboxaidfw.lovable.app
Continue developing this project in the Lovable editor.
main on GitHub and your changes sync back into Lovable, ready for your next prompt.Prefer working locally? You need Node.js and npm — install with nvm.
git clone <this-repository-url>
cd <repository-name>
npm i
npm run dev