What e.postData.contents actually is
When an external client sends a POST with a JSON body, Apps Script puts the raw request body string into e.postData.contents. That is a string, not an object. Every beginner hits e.postData.contents.name and gets undefined, then spends twenty minutes wondering why the data is missing. The fix is one line: var data = JSON.parse(e.postData.contents).
e.postData also has a type field you can check against 'application/json' if you want to be strict, but in practice I skip the check in internal tools and just parse. If the body is malformed, JSON.parse throws and the script errors out with a clear message in the Apps Script execution log — which is a better failure mode than silently writing undefined into your sheet.
One real gotcha: if the client sends the request as application/x-www-form-urlencoded instead of application/json, the body lands in e.parameter (as a key-value object) rather than e.postData.contents. This trips up people using default HTML form submissions. Either set the fetch content-type header explicitly to application/json on the client, or read from e.parameter instead.