Add example: DocScan Apps Script reference implementation
This commit is contained in:
@@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user