Every environment variable Agent Outlook reads, the confidential OIDC client it needs, and the manifest URL that must match both.
For self-hosting Agent Outlook. If you are only using an instance somebody else runs, you need none of this — start at Install.
| Variable | Purpose |
|---|---|
KEYCLOAK_URL | Base URL of the Keycloak instance. The issuer is {KEYCLOAK_URL}/realms/{KEYCLOAK_REALM}. |
KEYCLOAK_REALM | Realm name. Default agentics. |
KEYCLOAK_CLIENT_ID | Confidential client id. Default agentics-outlook. |
KEYCLOAK_CLIENT_SECRET | The client secret. Required — the app cannot complete a sign-in without it. |
PUBLIC_BASE_URL | This app's own public HTTPS URL. Used both as the OIDC redirect base and to render manifest.xml. |
USER_DATA_DIR | Root of the file-backed session store. /data in the container. |
AGENTICS_BASE_URL | Base URL of the main agentics.dk site — projects, stakeholders, interactions. |
SHARE_BASE_URL | Base URL of share.agentics.dk — Agent Share inboxes. |
OUTLOOK_PROJECT_MODE | "true" turns on the project step. Any other value, including unset, leaves it off. |
OUTLOOK_PROJECT_MODE is compared against the exact string "true". 1, yes
and True all read as off.
PUBLIC_BASE_URL has to match three thingsThis is the variable that breaks installs, because it appears in three places that must agree:
scripts/build-manifest.js renders public/manifest.xml
from it, and that is the URL Outlook loads the pane from.{PUBLIC_BASE_URL}/auth/callback, which must be
registered on the Keycloak client verbatim.Change the deployment URL and you must regenerate the manifest, update the
Keycloak client, and re-sideload. public/manifest.xml is deliberately not
checked in, precisely because it is per-environment.
/dataUSER_DATA_DIR holds the session store: the mapping from each opaque handle to
its tokens. Mount it on a volume.
If it is ephemeral, every restart silently signs everybody out — the handle in
each mailbox's roamingSettings survives, but the server no longer knows what
it refers to, so the pane asks people to connect again after every deploy.
Agent Outlook needs a confidential OIDC client in the realm:
agentics-outlook{PUBLIC_BASE_URL}/auth/callback, exactly oneConfidential rather than public PKCE because the app has a real server. Standard flow only, because nothing else is used: there is no password grant and no service account. The add-in forwards the signed-in user's own access token to both backends, so a service account would be authorised as nobody and every call would be rejected.
Create it with kcadm.sh:
kcadm.sh create clients -r agentics \
-s clientId=agentics-outlook \
-s enabled=true \
-s protocol=openid-connect \
-s publicClient=false \
-s standardFlowEnabled=true \
-s directAccessGrantsEnabled=false \
-s serviceAccountsEnabled=false \
-s 'redirectUris=["https://outlook.example.com/auth/callback"]'
Then read the generated secret from the Credentials tab (or
kcadm.sh get clients/<id>/client-secret -r agentics) and set it as
KEYCLOAK_CLIENT_SECRET. Never commit it.
These are not preferences — Outlook enforces them:
HTTPS with a valid certificate, publicly reachable. Outlook will not load a task pane from anything else.
A frame-ancestors policy that covers your Outlook hosts, and no
X-Frame-Options header. In Outlook on the web the pane is a cross-origin
iframe; on desktop it is a WebView, which is why an add-in can appear to work
on desktop while being completely broken on the web.
Microsoft is mid-migration of Microsoft 365 onto cloud.microsoft, so the list
must cover both the legacy *.office.com / *.office365.com origins and
the newer *.cloud.microsoft / *.microsoft365.com ones. Append; never
replace. Tenants on the old origins have not moved.
script-src must allow https://appsforoffice.microsoft.com — office.js
is loaded from Microsoft's CDN and is not bundled.
The shipped next.config.ts already sets all of this. It is listed here because
it is what to check first if the pane goes blank behind a reverse proxy that
adds its own headers.
Production is a Coolify git-build: Coolify clones the repository and builds
the Dockerfile itself. There is deliberately no registry image in the
production path, so a registry outage cannot block a deploy that never reads
from it.