const ADDON_CONFIG = { TRIAGE_API_URL: 'https://REPLACE_WITH_CLOUD_RUN_URL/api/triage', TRIAGE_API_KEY: 'REPLACE_WITH_TRIAGE_API_KEY' }; function buildAddonHome(e) { const section = CardService.newCardSection() .addWidget(CardService.newTextParagraph().setText( 'Mail Triage Assistant
Open any Gmail message to see AI triage, suggested actions, and a reply draft.' )); const card = CardService.newCardBuilder() .setHeader(CardService.newCardHeader().setTitle('Mail Triage Assistant')) .addSection(section) .build(); return card; } function buildContextCard(e) { try { const message = getOpenMessage_(e); const triage = callTriageApi_({ subject: message.getSubject() || '', sender: message.getFrom() || '', body: message.getPlainBody() || '' }); const actions = (triage.suggested_actions || []).join(', ') || 'None'; const section = CardService.newCardSection() .addWidget(CardService.newKeyValue().setTopLabel('Category').setContent(String(triage.category || 'unknown'))) .addWidget(CardService.newKeyValue().setTopLabel('Urgency').setContent(String(triage.urgency || 'unknown'))) .addWidget(CardService.newKeyValue().setTopLabel('Suggested Actions').setContent(actions)) .addWidget(CardService.newTextParagraph().setText('Reasoning:
' + sanitize_(triage.reasoning || 'N/A'))) .addWidget( CardService.newTextInput() .setFieldName('draftReply') .setTitle('Reply Draft') .setMultiline(true) .setValue(String(triage.suggested_reply || '')) ); const draftAction = CardService.newAction() .setFunctionName('createDraftReplyFromCard') .setParameters({ messageId: String(e.gmail.messageId || '') }); section.addWidget( CardService.newTextButton() .setText('Create Gmail Draft Reply') .setOnClickAction(draftAction) ); const card = CardService.newCardBuilder() .setHeader(CardService.newCardHeader().setTitle('AI Triage Results')) .addSection(section) .build(); return [card]; } catch (err) { const errorCard = CardService.newCardBuilder() .setHeader(CardService.newCardHeader().setTitle('Mail Triage Assistant')) .addSection( CardService.newCardSection() .addWidget(CardService.newTextParagraph().setText('Could not process this email.
' + sanitize_(String(err)))) ) .build(); return [errorCard]; } } function createDraftReplyFromCard(e) { const message = getOpenMessage_(e); const replyText = getFormText_(e, 'draftReply'); if (!replyText || !replyText.trim()) { return CardService.newActionResponseBuilder() .setNotification(CardService.newNotification().setText('Reply draft is empty.')) .build(); } message.createDraftReply(replyText); return CardService.newActionResponseBuilder() .setNotification(CardService.newNotification().setText('Draft reply created in Gmail.')) .build(); } function getOpenMessage_(e) { const accessToken = e && e.gmail && e.gmail.accessToken; const messageId = e && e.gmail && e.gmail.messageId; if (!accessToken || !messageId) { throw new Error('Missing Gmail event context (access token or message id).'); } GmailApp.setCurrentMessageAccessToken(accessToken); return GmailApp.getMessageById(messageId); } function getFormText_(e, fieldName) { try { return e.commonEventObject.formInputs[fieldName].stringInputs.value[0] || ''; } catch (err) { return ''; } } function sanitize_(s) { return String(s) .replace(/&/g, '&') .replace(//g, '>'); } function callTriageApi_(payload) { const response = UrlFetchApp.fetch(ADDON_CONFIG.TRIAGE_API_URL, { method: 'post', contentType: 'application/json', headers: { 'x-api-key': ADDON_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()); }