Files
gmail-inbox-architect/examples/docscan_appscript_example.gs
T

120 lines
4.0 KiB
JavaScript

/**
* Gmail Inbox Architect — DocScan Apps Script
* Reference implementation for attachment-based Gmail classification.
*
* Deploy in Google Apps Script (script.google.com). Run once manually
* to authorize Gmail scope, then set a time-driven trigger (hourly recommended).
*
* BEFORE RUNNING: Create these labels in Gmail if they don't exist:
* - Documentation
* - Z-Archive/DocScan (set to hidden in Gmail label settings)
*
* HOW IT WORKS:
* 1. Searches Gmail for recent threads with attachments not yet scanned
* 2. Inspects each attachment filename and MIME type
* 3. Applies "Documentation" label if attachment matches known patterns
* 4. Applies "Z-Archive/DocScan" guard label to ALL scanned threads
* (prevents re-scanning — this label appears even on non-matching threads)
*
* EXTENDING: Add new detection patterns by adding `if` branches in the
* attachment loop. See examples below.
*/
function scanDocumentationAttachments() {
const DOC_LABEL = getLabel_("Documentation"); // your target label
const SCAN_LABEL = getLabel_("Z-Archive/DocScan"); // hidden guard label
// Search criteria: recent, has attachment, not already scanned, not trash/spam
const query = [
"newer_than:14d",
"has:attachment",
"-in:trash",
"-in:spam",
'-label:"Z-Archive/DocScan"'
].join(" ");
const threads = GmailApp.search(query, 0, 50); // max 50 threads per run
for (const thread of threads) {
let applyDoc = false;
const reasons = [];
for (const message of thread.getMessages()) {
// Build text context for keyword matching (subject + body)
const text = [
message.getSubject() || "",
message.getPlainBody ? message.getPlainBody() : ""
].join("\n");
const hasDiagram = /\bdiagram\b/i.test(text);
for (const att of message.getAttachments({
includeInlineImages: false,
includeAttachments: true
})) {
const name = (att.getName() || "").toLowerCase();
const mime = (att.getContentType() || "").toLowerCase();
// --- Detection patterns --- add more here as needed ---
// Engineering STEP/STP files (CAD geometry)
if (/\.(stp|step)$/.test(name)) {
applyDoc = true;
reasons.push(`STEP file: ${name}`);
}
// PDF attachments when email body/subject mentions "diagram"
if (hasDiagram && (/\.pdf$/.test(name) || mime === "application/pdf")) {
applyDoc = true;
reasons.push(`diagram+PDF: ${name}`);
}
// --- Additional pattern examples (uncomment to enable) ---
// Vendor invoices by filename pattern
// if (/inv[-_]\d+\.pdf$/i.test(name)) {
// thread.addLabel(getLabel_("Finance/Invoices-Vendor-Payable"));
// reasons.push(`vendor invoice: ${name}`);
// }
// Purchase orders by filename pattern
// if (/\b(po|purchase.order)\b.*\.pdf$/i.test(name)) {
// thread.addLabel(getLabel_("Finance/Purchase-Orders"));
// reasons.push(`purchase order: ${name}`);
// }
// Signed contracts
// if (/(signed|executed).*\.pdf$/i.test(name)) {
// applyDoc = true;
// reasons.push(`signed contract: ${name}`);
// }
}
}
// Apply Documentation label if any pattern matched
if (applyDoc) {
thread.addLabel(DOC_LABEL);
console.log(
`Labeled Documentation — "${thread.getFirstMessageSubject()}" | ${reasons.join("; ")}`
);
}
// Always apply guard label (marks thread as scanned regardless of match)
thread.addLabel(SCAN_LABEL);
}
}
/**
* Helper: get a Gmail label by name, throw a clear error if it doesn't exist.
* Create labels in Gmail before running this script — the script will not create them.
*/
function getLabel_(name) {
const label = GmailApp.getUserLabelByName(name);
if (!label) {
throw new Error(
`Label not found: "${name}" — create it in Gmail settings before running this script.`
);
}
return label;
}