File size: 2,920 Bytes
3f3ebda
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
const CONFIG = {
  TRIAGE_API_URL: 'https://REPLACE_WITH_CLOUD_RUN_URL/api/triage',
  TRIAGE_API_KEY: 'REPLACE_WITH_TRIAGE_API_KEY',
  AUTO_SEND: false,
  MAX_THREADS_PER_RUN: 10,
  TRIAGED_LABEL: 'AI/Triaged',
  NEEDS_REVIEW_LABEL: 'AI/Needs-Review'
};

function triageInboxAndReply() {
  const triagedLabel = getOrCreateLabel_(CONFIG.TRIAGED_LABEL);
  const reviewLabel = getOrCreateLabel_(CONFIG.NEEDS_REVIEW_LABEL);
  const query = 'in:inbox is:unread -label:"' + CONFIG.TRIAGED_LABEL + '"';
  const threads = GmailApp.search(query, 0, CONFIG.MAX_THREADS_PER_RUN);

  for (const thread of threads) {
    try {
      const messages = thread.getMessages();
      const message = messages[messages.length - 1];
      const payload = {
        subject: message.getSubject() || '',
        sender: message.getFrom() || '',
        body: message.getPlainBody() || ''
      };

      const triage = callTriageApi_(payload);
      const hasReplyAction = (triage.suggested_actions || []).indexOf('reply') !== -1;

      if (hasReplyAction && triage.suggested_reply) {
        if (CONFIG.AUTO_SEND) {
          message.reply(triage.suggested_reply);
        } else {
          message.createDraftReply(triage.suggested_reply);
          reviewLabel.addToThread(thread);
        }
      }

      applyActionLabels_(thread, triage.suggested_actions || []);
      triagedLabel.addToThread(thread);
      message.markRead();
    } catch (error) {
      reviewLabel.addToThread(thread);
      Logger.log('Triage failed for thread ' + thread.getId() + ': ' + error);
    }
  }
}

function callTriageApi_(payload) {
  const response = UrlFetchApp.fetch(CONFIG.TRIAGE_API_URL, {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'x-api-key': CONFIG.TRIAGE_API_KEY
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  });

  const code = response.getResponseCode();
  if (code < 200 || code >= 300) {
    throw new Error('Triage API HTTP ' + code + ': ' + response.getContentText());
  }

  return JSON.parse(response.getContentText());
}

function applyActionLabels_(thread, actions) {
  const actionToLabel = {
    forward_to_it_sec: 'AI/Security',
    create_task: 'AI/Follow-Up',
    label_feedback: 'AI/Feedback',
    label_general: 'AI/General',
    move_to_junk: 'AI/Phishing',
    flag: 'AI/Important'
  };

  for (const action of actions) {
    if (actionToLabel[action]) {
      getOrCreateLabel_(actionToLabel[action]).addToThread(thread);
    }
  }
}

function getOrCreateLabel_(name) {
  return GmailApp.getUserLabelByName(name) || GmailApp.createLabel(name);
}

function installTriageTrigger() {
  ScriptApp.getProjectTriggers().forEach(trigger => {
    if (trigger.getHandlerFunction() === 'triageInboxAndReply') {
      ScriptApp.deleteTrigger(trigger);
    }
  });

  ScriptApp.newTrigger('triageInboxAndReply')
    .timeBased()
    .everyMinutes(5)
    .create();
}