Contact Form sent via Ajax with file upload returns 302 instead of 200 status code
Solved it (finally).

First part of the problem (the 302 redirect) was this missing configuration in the ajax call:

$.post({
  // ...
  dataType: 'json',
  // ...
};


It tells the server what you expect to get back from it.

Now this was fine, but I ran into a second issue. The xhr request stalled in Safari. When that happend and I reloaded the page (causing the request to finally stop), the email was sent anyways. The reason:

Safari seems to have an issue with this:

const formData = new FormData(formElement);


I found a hint here https://stackoverflow.com/a/8797289/2342963 that fixes the issue: appending the data manually to the FormData instance instead of passing the form element to the constructor.

So this is the working code:

const formData = new FormData();

$.each($('input, textarea, select'), function(i, element) {
    if (element.files) {
      $.each(element.files, function(i, file) {
        formData.append(element.name, file);
      });
    } else {
      formData.append(element.name, element.value);
    }
});

$.post({
    url: '/',
    data: formData,
    type: 'POST',
    cache: false,
    contentType: false,
    processData: false,
    dataType: 'json',

    success: response =&gt; {
        if (response.success) {
        // Show success
        } else {
        // Show error
        }
    },
    error: () =&gt; {
        // Show error
    }
});


Turns out, it wasn’t a Craft issue after all ¯_(ツ)_/¯