The trigger you install determines what shape your data arrives in
Google Forms can fire two different onFormSubmit triggers, and they are not interchangeable. If you open the script editor from inside the Form (Extensions → Apps Script), you get the Form-bound script container. If you open it from a linked Sheet, you are in a different container with a different event object. The Form-bound trigger delivers e.response, a FormResponse object, from which you call getItemResponses() to get an array of ItemResponse objects — one per question. The Sheet-bound trigger delivers e.values, a flat array of strings in column order, with a timestamp jammed in position zero.
That shape difference matters the moment you have a question with no answer (skipped optional field). In e.values the skipped column is an empty string, and your column-to-question mapping silently shifts if you ever reorder questions. In e.response.getItemResponses(), the skipped item is simply absent from the array, so you can always call getItem().getTitle() to know which question you are looking at. For a summary email, the Form-bound path is less fragile.
The first time I wired this up I used a simple trigger named onFormSubmit (no installation step), and it never fired. Simple triggers in Forms do not receive the event object for form submissions — they require the installable version, which you register through the Triggers panel.