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
- 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.
- Verify direct egress from the host: curl -sS https://api.mailgun.net/v3/<your-domain>/messages from the machine running the daemons.
- Check the mailer's 'domain' and 'api-key' options ('bin/config get cluster.mailers') - a wrong domain commonly produces non-JSON 404 pages.
- 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
- Whitelist mail API hosts (api.mailgun.net) on egress firewalls and exempt them from TLS inspection.
- Smoke-test the mailer with 'bin/mail send-test' after any network or config change.
- Monitor the mail queue failure rate - a spike of JSON-decode failures indicates proxy/egress problems, not bad mail.
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
- Expected JSON response from Twilio.
- Buildkite did not return a "%s"!
- Request failed with errors: %s.
- Mail signature is not valid. Check your Mailgun API key.
- Configuration file is not properly formatted JSON. %s
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/64f66f0975205ad6.
Report an issue: GitHub.