Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .dev.vars.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# Cloudflare Access (required in production). TEAM_DOMAIN may be the base Access URL or the full /cdn-cgi/access/certs URL.
POLICY_AUD=your-access-policy-audience-tag
TEAM_DOMAIN=https://your-team.cloudflareaccess.com
# Optional Google Drive import. Share selected files with this service account.
GOOGLE_SERVICE_ACCOUNT_EMAIL=service-account@example.iam.gserviceaccount.com
GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----\\n"
# Optional Microsoft Graph app-only OneDrive import.
MICROSOFT_TENANT_ID=your-tenant-id
MICROSOFT_CLIENT_ID=your-app-client-id
MICROSOFT_CLIENT_SECRET=your-app-client-secret
ONEDRIVE_USER_ID=person@example.com
# Microsoft OAuth (required for the Outlook connector)
MICROSOFT_TENANT_ID=common
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
MICROSOFT_REDIRECT_URI=http://localhost:5173/auth/microsoft/callback
APP_ORIGIN=http://localhost:5173
Comment on lines +7 to +17
# Generate a separate random value for production token encryption.
TOKEN_ENCRYPTION_KEY=
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ https://github.com/cloudflare/agentic-inbox/issues/4#issuecomment-4269118513
- **Built-in AI agent** — Side panel with 9 email tools for reading, searching, drafting, and sending
- **Auto-draft on new email** — Agent automatically reads inbound emails and generates draft replies, always requiring explicit confirmation before sending
- **Configurable and persistent** — Custom system prompts per mailbox, persistent chat history, streaming markdown responses, and tool call visibility
- **Evidence-backed memory** — Markdown/file memory with keyword and optional semantic retrieval, source provenance, confirmed facts, Google Drive imports, and operator-visible drafting context

## Stack

Expand All @@ -62,6 +63,12 @@ npm run dev
1. Set your domain in `wrangler.jsonc`
2. Create an R2 bucket named `agentic-inbox`: `wrangler r2 bucket create agentic-inbox`

### Optional cloud-drive memory imports

Configure `GOOGLE_SERVICE_ACCOUNT_EMAIL` and `GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY` as Worker secrets, then share selected Drive files with that service account. In Memory, choose **Google Drive** and paste the selected file IDs. Imports are mailbox-scoped, deduplicated by Drive file ID, converted to Markdown-compatible text, chunked, and indexed through the same pipeline as uploaded files.

For Microsoft OneDrive, configure `MICROSOFT_TENANT_ID`, `MICROSOFT_CLIENT_ID`, `MICROSOFT_CLIENT_SECRET`, and `ONEDRIVE_USER_ID` as Worker secrets. Grant the Microsoft Entra app the least-privilege Microsoft Graph application permission needed to read the configured user drive, then choose **Microsoft OneDrive** in Memory and paste selected file IDs. OneDrive imports use the same normalization, chunking, provenance, deduplication, and fact-review pipeline.

### Deploy

```bash
Expand Down
132 changes: 132 additions & 0 deletions add-in/manifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Replace YOUR-ADDIN-HOST.example.com with the HTTPS host that serves /add-in/. -->
<OfficeApp
xmlns="http://schemas.microsoft.com/office/appforoffice/1.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0"
xsi:type="MailApp">
<Id>c4ae0c81-89f5-40d3-b8f8-e1eaaef82f66</Id>
<Version>1.0.0.0</Version>
<ProviderName>Cloudflare</ProviderName>
<DefaultLocale>en-US</DefaultLocale>
<DisplayName DefaultValue="Agentic Inbox" />
<Description DefaultValue="Open Agentic Inbox with Outlook message context." />
<IconUrl DefaultValue="https://YOUR-ADDIN-HOST.example.com/favicon.ico" />
<HighResolutionIconUrl DefaultValue="https://YOUR-ADDIN-HOST.example.com/favicon.ico" />
<SupportUrl DefaultValue="https://YOUR-ADDIN-HOST.example.com/" />
<AppDomains>
<AppDomain>https://YOUR-ADDIN-HOST.example.com</AppDomain>
</AppDomains>
<Hosts>
<Host Name="Mailbox" />
</Hosts>
<Requirements>
<Sets>
<Set Name="Mailbox" MinVersion="1.5" />
</Sets>
</Requirements>
<FormSettings>
<Form xsi:type="ItemRead">
<DesktopSettings>
<SourceLocation DefaultValue="https://YOUR-ADDIN-HOST.example.com/add-in/taskpane.html?hostContext=read&amp;appRoot=current&amp;appPath=/" />
<RequestedHeight>320</RequestedHeight>
</DesktopSettings>
</Form>
<Form xsi:type="ItemEdit">
<DesktopSettings>
<SourceLocation DefaultValue="https://YOUR-ADDIN-HOST.example.com/add-in/taskpane.html?hostContext=compose&amp;appRoot=current&amp;appPath=/" />
<RequestedHeight>320</RequestedHeight>
</DesktopSettings>
</Form>
</FormSettings>
<Permissions>ReadWriteItem</Permissions>
<Rule xsi:type="RuleCollection" Mode="Or">
<Rule xsi:type="ItemIs" ItemType="Message" FormType="Read" />
<Rule xsi:type="ItemIs" ItemType="Message" FormType="Edit" />
</Rule>
<DisableEntityHighlighting>false</DisableEntityHighlighting>
<VersionOverrides
xmlns="http://schemas.microsoft.com/office/mailappversionoverrides/1.1"
xsi:type="VersionOverridesV1_1">
<Description resid="Commands.Description" />
<Requirements>
<bt:Sets DefaultMinVersion="1.5">
<bt:Set Name="Mailbox" />
</bt:Sets>
</Requirements>
<Hosts>
<Host xsi:type="MailHost">
<DesktopFormFactor>
<FunctionFile resid="Taskpane.Read.Url" />
<ExtensionPoint xsi:type="MessageReadCommandSurface">
<OfficeTab id="TabDefault">
<Group id="ReadGroup">
<Label resid="Commands.GroupLabel" />
<Control xsi:type="Button" id="ReadOpenTaskpane">
<Label resid="Commands.ReadButtonLabel" />
<Supertip>
<Title resid="Commands.ReadButtonLabel" />
<Description resid="Commands.ReadButtonDescription" />
</Supertip>
<Icon>
<bt:Image size="16" resid="Icon.16" />
<bt:Image size="32" resid="Icon.32" />
<bt:Image size="80" resid="Icon.80" />
</Icon>
<Action xsi:type="ShowTaskpane">
<SourceLocation resid="Taskpane.Read.Url" />
<SupportsPinning>true</SupportsPinning>
</Action>
</Control>
</Group>
</OfficeTab>
</ExtensionPoint>
<ExtensionPoint xsi:type="MessageComposeCommandSurface">
<OfficeTab id="TabDefault">
<Group id="ComposeGroup">
<Label resid="Commands.GroupLabel" />
<Control xsi:type="Button" id="ComposeOpenTaskpane">
<Label resid="Commands.ComposeButtonLabel" />
<Supertip>
<Title resid="Commands.ComposeButtonLabel" />
<Description resid="Commands.ComposeButtonDescription" />
</Supertip>
<Icon>
<bt:Image size="16" resid="Icon.16" />
<bt:Image size="32" resid="Icon.32" />
<bt:Image size="80" resid="Icon.80" />
</Icon>
<Action xsi:type="ShowTaskpane">
<SourceLocation resid="Taskpane.Compose.Url" />
<SupportsPinning>true</SupportsPinning>
</Action>
</Control>
</Group>
</OfficeTab>
</ExtensionPoint>
</DesktopFormFactor>
</Host>
</Hosts>
<Resources>
<bt:Images>
<bt:Image id="Icon.16" DefaultValue="https://YOUR-ADDIN-HOST.example.com/favicon.ico" />
<bt:Image id="Icon.32" DefaultValue="https://YOUR-ADDIN-HOST.example.com/favicon.ico" />
<bt:Image id="Icon.80" DefaultValue="https://YOUR-ADDIN-HOST.example.com/favicon.ico" />
</bt:Images>
<bt:Urls>
<bt:Url id="Taskpane.Read.Url" DefaultValue="https://YOUR-ADDIN-HOST.example.com/add-in/taskpane.html?hostContext=read&amp;appRoot=current&amp;appPath=/" />
<bt:Url id="Taskpane.Compose.Url" DefaultValue="https://YOUR-ADDIN-HOST.example.com/add-in/taskpane.html?hostContext=compose&amp;appRoot=current&amp;appPath=/" />
</bt:Urls>
<bt:ShortStrings>
<bt:String id="Commands.GroupLabel" DefaultValue="Agentic Inbox" />
<bt:String id="Commands.ReadButtonLabel" DefaultValue="Open Inbox" />
<bt:String id="Commands.ComposeButtonLabel" DefaultValue="Open Inbox" />
</bt:ShortStrings>
<bt:LongStrings>
<bt:String id="Commands.Description" DefaultValue="Open Agentic Inbox with the current Outlook item context." />
<bt:String id="Commands.ReadButtonDescription" DefaultValue="Inspect the current message and open Agentic Inbox." />
<bt:String id="Commands.ComposeButtonDescription" DefaultValue="Inspect the draft and open Agentic Inbox." />
</bt:LongStrings>
</Resources>
</VersionOverrides>
</OfficeApp>
211 changes: 211 additions & 0 deletions add-in/taskpane.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
:root {
color-scheme: light;
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
--bg: #f3f7fb;
--panel: #ffffff;
--panel-border: #d8e2ee;
--text: #16324a;
--muted: #5a7188;
--accent: #0f6cbd;
--accent-strong: #0c5aa0;
--accent-soft: #e8f2fc;
--warning: #8a4b0f;
--warning-soft: #fff5e8;
--success: #0f6a46;
--success-soft: #e8f6f0;
}

* {
box-sizing: border-box;
}

html,
body {
margin: 0;
min-height: 100%;
background: radial-gradient(circle at top, #ffffff 0%, var(--bg) 58%);
color: var(--text);
}

body {
font: 14px/1.5 "Segoe UI", "Helvetica Neue", Arial, sans-serif;
}

.shell {
min-height: 100vh;
padding: 16px;
}

.panel {
max-width: 480px;
margin: 0 auto;
padding: 18px;
background: var(--panel);
border: 1px solid var(--panel-border);
border-radius: 18px;
box-shadow: 0 18px 38px rgba(15, 49, 85, 0.08);
}

.eyebrow-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}

.eyebrow {
margin: 0;
color: var(--muted);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}

.status-badge {
display: inline-flex;
align-items: center;
min-height: 28px;
padding: 0 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
}

.status-pending {
color: var(--warning);
background: var(--warning-soft);
}

.status-ready {
color: var(--success);
background: var(--success-soft);
}

.status-warning {
color: var(--warning);
background: var(--warning-soft);
}

h1 {
margin: 10px 0 4px;
font-size: 24px;
line-height: 1.2;
}

.lede {
margin: 0 0 18px;
color: var(--muted);
}

.context-list {
margin: 0;
padding: 0;
}

.context-row {
display: grid;
grid-template-columns: 72px 1fr;
gap: 10px;
padding: 11px 0;
border-top: 1px solid #edf2f7;
}

.context-row:first-child {
border-top: 0;
}

.context-row dt {
color: var(--muted);
font-weight: 600;
}

.context-row dd {
margin: 0;
font-weight: 600;
overflow-wrap: anywhere;
}

.actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 18px;
}

.primary-action,
.secondary-action {
appearance: none;
border-radius: 12px;
font: inherit;
font-weight: 700;
text-decoration: none;
cursor: pointer;
transition: transform 120ms ease, background-color 120ms ease, border-color 120ms ease;
}

.primary-action {
padding: 11px 14px;
color: #ffffff;
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-strong) 100%);
border: 0;
}

.secondary-action {
padding: 10px 14px;
color: var(--text);
background: var(--accent-soft);
border: 1px solid #cfe1f5;
}

.primary-action:hover,
.secondary-action:hover {
transform: translateY(-1px);
}

.destination {
margin-top: 16px;
padding: 12px;
border-radius: 14px;
background: #f7fafc;
border: 1px solid #e6eef6;
}

.destination-label {
display: block;
margin-bottom: 6px;
color: var(--muted);
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
}

code {
display: block;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 12px;
overflow-wrap: anywhere;
}

.note {
margin: 14px 0 0;
color: var(--muted);
font-size: 12px;
}

@media (max-width: 480px) {
.shell {
padding: 12px;
}

.panel {
padding: 16px;
border-radius: 16px;
}

.context-row {
grid-template-columns: 1fr;
gap: 4px;
}
}
Loading