Retool Email: Created .csv file with Table Data. How to automate sending email with created .csv file?

Hello,

I've created a query that generates a nice .csv file with totals for my end-user. They can download it by clicking a button in the application. However, instead of manually downloading the file, they want to receive it by email on a daily base.

I want to use Retool Email, however I struggle a bit with setting it up with Retool Email.
I could either create a query in the application that sends out the report and runs every 24hours or create a new workflow that sends the email daily on a set time with the generated .csv file.

How do I automatically generate and attach the .csv file to the email with Retool Email?
Also, is there a limit on how many emails can be send with Retool Email on a daily base?

The above is what you should do and I believe the limit is 120 emails per hour.

As for attaching the file in the workflow:

1 Like

Thanks for the prompt reply.
Good to know about the limit, that's enough for this purpose.

With Retool Email I've noticed how to attach files, however I struggled with fx mode.
The query I use to generate the .csv file to download is the one below.
Would you know how I can update it to get the right data response, so I can attach it with fx mode in Retool Email?

const dataFromReservationsTableSource = formatDataAsArray(reservations_table.data);
const dataFromIncludedSource = formatDataAsArray(NewsumAllTotals.data.sumtotalincluded);
const dataFromNotIncludedSource = formatDataAsArray(NewsumAllTotals.data.sumtotalnotincluded);
const dataFromArrivedSource = formatDataAsArray (NewsumAllTotals.data.sumtotalarrived);
const dataFromExpectedSource = formatDataAsArray(NewsumAllTotals.data.sumtotalexpected);


function formatTimeToHHMM(timeString) {
  if (!timeString) return ""; 
  const match = timeString.match(/\d{2}:\d{2}/); 
  return match ? match[0] : ""; 
}

const filteredReservationsTableData = dataFromReservationsTableSource.map(({ reservation, included, arrived, arrivaltime }) => ({
  reservation,
  "breakfast included": included,
  arrived,
  arrivaltime: formatTimeToHHMM(arrivaltime), 
}));

const columnNames = Object.keys(filteredReservationsTableData[0]);
const breakfastValues = filteredReservationsTableData.map(row => Object.values(row));

const summaryData = [
  {
    "BREAKFASTLIST": "Breakfast List Today (" + getCurrentDate() + ")",
    "TOTAL INCLUDED": dataFromIncludedSource[0][Object.keys(dataFromIncludedSource[0])[0]],
    "TOTAL NOT INCLUDED": dataFromNotIncludedSource[0][Object.keys(dataFromNotIncludedSource[0])[0]],
    "ARRIVED": dataFromArrivedSource[0][Object.keys(dataFromArrivedSource[0])[0]],
    "NO SHOWS": dataFromExpectedSource[0][Object.keys(dataFromExpectedSource[0])[0]],
  },
  {}, // Blank row for spacing
];

const combinedData = [...summaryData, ...[columnNames.reduce((obj, colName) => ({ ...obj, [colName]: colName }), {}), ...breakfastValues]];

function getCurrentDate() {
  const today = new Date();
  const options = { year: 'numeric', month: 'long', day: 'numeric' };
  return today.toLocaleDateString(undefined, options);
}

const csvContent =
  "data:text/csv;charset=utf-8," +
  [Object.keys(combinedData[0]).join(",")].concat(combinedData.map(row => Object.values(row).join(","))).join("\n");

const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "BreakfastList_" + getCurrentDate() + ".csv");
document.body.appendChild(link); 
link.click();