Forum Discussion

scomesana_v's avatar
scomesana_v
Occasional Reader
May 25, 2026

Classic Outlook event-based add-in: fetch/XMLHttpRequest fails in JavaScript-only runtime

We are testing outbound HTTP from an Outlook add-in event-based activation handler running in the classic Outlook for Windows JavaScript-only runtime used by:

<Override type="javascript" resid="JSRuntime.Url"/>

Question

Is response-bearing HTTP via fetch or XMLHttpRequest officially supported from the classic Outlook for Windows JavaScript-only event runtime?

If it is supported:

  • Are there additional manifest, policy, trust, or runtime requirements beyond:
    • absolute HTTPS URLs,
    • <AppDomains>,
    • .well-known/microsoft-officeaddins-allowed.json,
    • Office.actions.associate(...),
    • and using a single bundled JS file?

If it is not supported or unreliable in this runtime:

  • What is the recommended/supported pattern for an OnAppointmentSend handler that requires a server-side validation decision before allowing send?

We are currently considering this workaround architecture:

  1. Precompute the validation result in the taskpane/WebView runtime.
  2. Store the decision in Outlook custom properties.
  3. Read the stored value from events.js during OnAppointmentSend.

Is this workaround required, or should direct outbound HTTP from the event runtime work reliably?

Environment

Client tested:

  • Microsoft Outlook 2016 MSO
  • Version 2604
  • Build 16.0.19929.20172
  • 32-bit

Platform logged by Office.js:

Office.context.platform === "PC"

The launch event itself works correctly:

  • OnAppointmentSendHandler is invoked.
  • Office.actions.associate(...) works.
  • event.completed(...) is delivered to Outlook successfully.

The issue is specifically that response-bearing HTTP from events.js does not complete reliably.

Relevant manifest excerpt

<VersionOverrides
  xmlns="http://schemas.microsoft.com/office/mailappversionoverrides/1.1"
  xsi:type="VersionOverridesV1_1">

  <Requirements>
    <bt:Sets DefaultMinVersion="1.12">
      <bt:Set Name="Mailbox" />
    </bt:Sets>
  </Requirements>

  <Hosts>
    <Host xsi:type="MailHost">

      <Runtimes>
        <Runtime resid="WebViewRuntime.Url" lifetime="short">
          <Override type="javascript" resid="JSRuntime.Url" />
        </Runtime>
      </Runtimes>

      <DesktopFormFactor>
        <ExtensionPoint xsi:type="LaunchEvent">
          <LaunchEvents>
            <LaunchEvent
              Type="OnAppointmentSend"
              FunctionName="OnAppointmentSendHandler"
              SendMode="SoftBlock" />
          </LaunchEvents>

          <SourceLocation resid="WebViewRuntime.Url" />
        </ExtensionPoint>
      </DesktopFormFactor>

    </Host>
  </Hosts>

  <Resources>
    <bt:Urls>
      <bt:Url
        id="WebViewRuntime.Url"
        DefaultValue="https://example-addin-host.example.com" />

      <bt:Url
        id="JSRuntime.Url"
        DefaultValue="https://example-addin-host.example.com/events.js?v=2.0.0.16" />
    </bt:Urls>
  </Resources>

</VersionOverrides>

 

Well-known event runtime CORS configuration

We also configured the documented well-known URI for event runtime CORS:

{ "allowed": [ "https://example-addin-host.example.com/events.js?v=2.0.0.16" ] }

Hosted at:

https://example-addin-host.example.com/.well-known/microsoft-officeaddins-allowed.json

Minimal events.js repro

(function (root) {

  function OnAppointmentSendHandler(event) {
    console.log('[test] handler triggered', {
      hasFetch: typeof fetch,
      hasXMLHttpRequest: typeof XMLHttpRequest,
      platform: Office.context.platform
    });

    var fetchStartedAt = Date.now();

    setTimeout(function () {
      console.log('[test] fetch still pending', {
        elapsedMs: Date.now() - fetchStartedAt
      });
    }, 4500);

    fetch('https://example-addin-host.example.com/manifest.xml', {
      method: 'GET',
      cache: 'no-store'
    }).then(
      function (response) {
        console.log('[test] fetch response', {
          elapsedMs: Date.now() - fetchStartedAt,
          ok: response.ok,
          status: response.status,
          statusText: response.statusText,
          url: response.url
        });

        return response.text();
      },
      function (error) {
        console.log('[test] fetch error', {
          elapsedMs: Date.now() - fetchStartedAt,
          name: error && error.name,
          message: error && error.message
        });
      }
    );

    var xhrStartedAt = Date.now();
    var xhr = new XMLHttpRequest();

    xhr.timeout = 4500;

    xhr.onreadystatechange = function () {
      console.log('[test] xhr readyState', {
        elapsedMs: Date.now() - xhrStartedAt,
        readyState: xhr.readyState,
        status: xhr.status,
        statusText: xhr.statusText,
        responseURL: xhr.responseURL,
        responseTextLength: xhr.responseText ? xhr.responseText.length : 0
      });
    };

    xhr.onload = function () {
      console.log('[test] xhr load', {
        elapsedMs: Date.now() - xhrStartedAt,
        status: xhr.status,
        responseTextLength: xhr.responseText ? xhr.responseText.length : 0
      });
    };

    xhr.onerror = function () {
      console.log('[test] xhr error', {
        elapsedMs: Date.now() - xhrStartedAt,
        status: xhr.status
      });
    };

    xhr.ontimeout = function () {
      console.log('[test] xhr timeout', {
        elapsedMs: Date.now() - xhrStartedAt,
        readyState: xhr.readyState,
        status: xhr.status
      });
    };

    xhr.open(
      'GET',
      'https://example-addin-host.example.com/manifest.xml',
      true
    );

    xhr.send();

    setTimeout(function () {
      console.log('[test] completing event after wait');
      event.completed({ allowEvent: true });
    }, 6500);
  }

  root.OnAppointmentSendHandler = OnAppointmentSendHandler;

  Office.actions.associate(
    'OnAppointmentSendHandler',
    OnAppointmentSendHandler
  );

})(this);

 

Observed behavior

  • The handler logs successfully.
  • typeof fetch === "function".
  • typeof XMLHttpRequest === "function".

However:

  • fetch(...) starts, but no response callback is logged.
  • In earlier tests, fetch remained pending until our timeout marker.
  • XMLHttpRequest reaches readyState: 1 after open(), but does not return useful response data.
  • In some runs it eventually reports:
    • status: 0,
    • empty response,
    • or timeout.
  • Server-side logs show the request does not reach the server in the failing cases.
  • event.completed(...) works and Outlook receives the completion.

Things already ruled out / tested

  • We are using absolute URLs, not relative URLs.
  • We tested both:
    • https://localhost:3000/...
    • http://localhost:3000/...
  • We tested through an HTTPS tunnel pointing to localhost.
  • We tested trusted local HTTPS certificates; this does not appear to be a certificate trust issue.
  • We configured .well-known/microsoft-officeaddins-allowed.json as documented for event-based CORS.
  • We added the target domains to <AppDomains>.
  • We tested public safe domains such as JSONPlaceholder-style endpoints.
  • This does not appear to be ordinary CORS because in failing cases the request never reaches the server.
  • The events.js bundle is intentionally simple and compatible with older JavaScript syntax.
  • We avoided modern syntax and can provide an ES5/ES2015 version if needed.
  • The runtime itself loads and executes the handler correctly.
No RepliesBe the first to reply