Bug report: resource.rawRequest() fails with "Truncated result: headers line not terminated" against Mailchimp (HTTP/2 + gzip)
Summary
In a Retool React app, a backend function that calls a REST API resource via myResource.rawRequest({...}) fails on every request to the Mailchimp Marketing API with:
Truncated result: headers line not terminated
The identical HTTP requests succeed when run with curl from outside Retool. A different REST resource in the same app (Airtable) works fine through the same rawRequest() pattern, so the issue appears specific to the Mailchimp endpoint's response (HTTP/2, gzip, served behind istio-envoy). We suspect Retool's outbound HTTP client mis-parses that response.
We need to know the supported way to make this call succeed from a backend function (correct rawRequest usage, a resource setting, or an alternative such as fetch).
Environment
- Retool React app (built with the Retool app builder/agent).
- Backend function (TypeScript) in the app, calling a REST resource binding:
mailchimp.rawRequest({ path, method, headers, body }). - Resource: REST API, Manual Queries.
- Base URL:
https://<YOUR_DC>.api.mailchimp.com/3.0/(e.g.https://us19.api.mailchimp.com/3.0/) - Authentication: Basic Auth β Username:
anystring, Password:<YOUR_MAILCHIMP_API_KEY>
- Base URL:
- Environment: production.
The failing call (exact)
The backend function makes calls like:
// PUT upsert a member
await mailchimp.rawRequest({
path: `lists/<YOUR_LIST_ID>/members/<md5_lowercase_email>`,
method: 'PUT',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: { email_address: 'x@example.com', status_if_new: 'subscribed', merge_fields: { FNAME: 'X', LNAME: 'Y' } },
})
Result: throws Truncated result: headers line not terminated. Same for POST .../tags, DELETE .../members/<hash>, and (we believe) even a simple GET ping β see minimal repro below.
Minimal reproduction (please fill in your own values)
Create a REST resource named mailchimp (Base URL + Basic Auth as above). Then add this backend function and run it:
// Backend function: reproMailchimp
export default async function reproMailchimp() {
// Simplest possible Mailchimp call: GET /3.0/ping (no body)
const res = await mailchimp.rawRequest({
path: 'ping',
method: 'GET',
headers: { Accept: 'application/json' },
})
return res
}
- Expected:
{ "health_status": "Everything's Chimpy!" } - Actual: throws
Truncated result: headers line not terminated
Baseline that works (same request, via curl)
Run from any shell with the same credentials:
dc="<YOUR_DC>" # e.g. us19
apikey="<YOUR_MAILCHIMP_API_KEY>"
curl -sS "https://${dc}.api.mailchimp.com/3.0/ping" --user "anystring:${apikey}"
# => {"health_status":"Everything's Chimpy!"} (HTTP 200)
A full write also works via curl (so the API, key, and payloads are valid):
hash=$(printf 'x@example.com' | md5sum | cut -d' ' -f1)
curl -sS --user "anystring:${apikey}" -H "Content-Type: application/json" \
-X PUT "https://${dc}.api.mailchimp.com/3.0/lists/<YOUR_LIST_ID>/members/${hash}" \
-d '{"email_address":"x@example.com","status_if_new":"subscribed","merge_fields":{"FNAME":"X","LNAME":"Y"}}'
# => 200, member JSON
Likely-relevant detail: the response is HTTP/2 + gzip behind istio-envoy
Inspecting the response headers directly:
curl -sS -D - -o /dev/null --user "anystring:${apikey}" \
"https://${dc}.api.mailchimp.com/3.0/ping"
HTTP/2 200
content-type: application/json; charset=utf-8
content-encoding: gzip
vary: Accept-Encoding
server: istio-envoy
"headers line not terminated" is a response-header parsing failure. Our working hypothesis is that Retool's rawRequest HTTP client mishandles this endpoint's HTTP/2 and/or gzip/chunked response from Mailchimp's istio-envoy edge.
What we have already ruled out
- Wrong URL / scheme: Base URL is exactly
https://<dc>.api.mailchimp.com/3.0/(https, trailing slash). Confirmed. - Auth: Basic Auth with the API key is correct; with no auth Mailchimp returns a clean
401, not this error. (Bearer also authenticates.) The error is identical regardless, so it is not auth. - Merge fields / payload: The exact bodies succeed via curl (200/204), and the required merge fields exist on the audience.
- Concurrency: We reduced the backend function to one request at a time (no parallel
rawRequestcalls). No change. - Headers: Adding
Content-Type: application/json,Accept: application/json, andAccept-Encoding: identitydid not change it. - A different REST resource (Airtable) in the same app works through the same
rawRequestpattern β sorawRequestis not universally broken; this is specific to the Mailchimp endpoint's response.
Questions for Retool
- What is the supported way to make this call succeed from a React-app backend function β correct
rawRequestoptions, a resource setting (e.g. force HTTP/1.1, disable compression), or an alternative API? - Is global
fetch()available/permitted inside a backend function (to bypassrawRequest)? If so, is that recommended? - Is "Truncated result: headers line not terminated" a known issue with HTTP/2 + gzip (istio-envoy) upstreams, and is there a fix or workaround?
Appendix: actual failure output (one run, 64 contacts)
Every contact fails identically:
Β· Loading contactsβ¦ Loaded 64 contactsβ¦ Syncingβ¦
β a@example.org β error Β· Truncated result: headers line not terminated
β b@example.org β error Β· Truncated result: headers line not terminated
β¦ (all 64) β¦
Β· Completed with 64 errors.