What appendTable actually expects
The Body class in Apps Script exposes appendTable() with two signatures: no arguments (creates an empty table you populate cell by cell afterward) and a 2D array of strings. The second form is what you want when you already have data — it creates the table and fills every cell in a single call.
The word 'strings' is load-bearing. The parameter type is String[][], not any[][], and the runtime enforces it strictly. Pass a raw number like 12 and you get a TypeError at the appendTable() call, not a silent coercion. This is the first place people get burned coming from Sheets, which accepts mixed-type ranges without complaint.
Wrap every non-string value in String() before building the array. For Dates, String(new Date()) gives you a locale-formatted string; if you want a specific format, run Utilities.formatDate() first and pass that result instead.
The table lands at the very end of the document body, after the last paragraph. If you need it inserted at a specific position — say, after a named bookmark — you have to use insertTable(childIndex, rows) instead, which takes a zero-based index into the body's child elements.