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