phacility/phabricator · error · PhutilProxyException

Failed to JSON decode response.

Error message

Failed to JSON decode response.

What it means

After POSTing a message to Mailgun's API, PhabricatorMailMailgunAdapter resolves the HTTP future and decodes the body with phutil_json_decode(). The body failed to parse, so the adapter wraps the PhutilJSONParserException in a PhutilProxyException with this message - meaning Mailgun (or something between Phabricator and Mailgun, such as a proxy or captive portal) returned HTML, an empty body, or truncated output instead of JSON.

Source

Thrown at src/applications/metamta/adapter/PhabricatorMailMailgunAdapter.php:122

      ->setHTTPBasicAuthCredentials('api', new PhutilOpaqueEnvelope($api_key))
      ->setTimeout(60);

    $attachments = $message->getAttachments();
    foreach ($attachments as $attachment) {
      $future->attachFileData(
        'attachment',
        $attachment->getData(),
        $attachment->getFilename(),
        $attachment->getMimeType());
    }

    list($body) = $future->resolvex();

    $response = null;
    try {
      $response = phutil_json_decode($body);
    } catch (PhutilJSONParserException $ex) {
      throw new PhutilProxyException(
        pht('Failed to JSON decode response.'),
        $ex);
    }

    if (!idx($response, 'id')) {
      $message = $response['message'];
      throw new Exception(
        pht(
          'Request failed with errors: %s.',
          $message));
    }
  }

}

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Read the chained exception: the PhutilJSONParserException message contains the actual bytes that failed to parse - find it in the daemon log or the mail's delivery error - and identify whether it is a proxy page, an HTML error, or an empty body.
  2. Verify direct egress from the host: curl -sS https://api.mailgun.net/v3/<your-domain>/messages from the machine running the daemons.
  3. Check the mailer's 'domain' and 'api-key' options ('bin/config get cluster.mailers') - a wrong domain commonly produces non-JSON 404 pages.
  4. If a proxy is mandatory, exempt api.mailgun.net from interception, or switch that mailer to type 'smtp' and let Mailgun's SMTP relay handle delivery.
Defensive patterns

Strategy: retry

Try / catch

try {
  $adapter->sendMessage($message);
} catch (PhutilProxyException $ex) {
  if (preg_match('/Failed to JSON decode response/', $ex->getMessage())) {
    // Transport/proxy interference: log $ex->getPrevious() (contains the raw
    // body) and retry with backoff; alert if failures persist.
    return retry_later($message);
  }
  throw $ex;
}

Prevention

When it happens

Trigger: Sending mail through the mailgun adapter (mail workers, 'bin/mail send-test') while an intercepting egress proxy returns an HTML block page for api.mailgun.net; Mailgun itself returning a non-JSON error page (5xx, rate-limit page); a wrong 'domain' option resolving to a host that answers with HTML; truncated responses from network middleware.

Common situations: Egress-filtered corporate/server environments where api.mailgun.net is not directly reachable; SSL/TLS inspection rewriting responses; a typo'd domain option; transient Mailgun incidents. Because this is a plain Exception, the PhabricatorMetaMTAWorker treats the send as a temporary failure and retries it.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/64f66f0975205ad6. Report an issue: GitHub.