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());
}