Sitemap
Gabriel Cruz

Gabriel Cruz

Customer Success Leader at Scaffold Education

Code Meets Customer Success: Automating CSAT Surveys using Gemini and Google Apps Script

5 min readMar 13, 2026

It’s not just about another automated task; it’s about how to use technology to support a customer-centric culture with what you have in your hands.

Press enter or click to view image in full size
(just a nano banana image to bring color here)

If you are a CSM or look after customers in different contexts, caring about what your customers think about your service or how their expectations are met must be a big deal to you, for a few reasons:

  • Evidence of your customer experience: No one speaks for the client better than the client themselves (dashboards, KPIs, etc. — they help, but the customer’s voice is what changes the game at the end of the day).
  • Collecting direct and constant feedback along your customer journey helps you improve your service quality and change course.
  • If it’s a high-value business (B2B), it isn’t just about your software and how the features are helping or not: it’s about how your focal point feels that you care about their own results and how they are led to achieve their goals. If another software in the market is better than ours, maybe your customers choose you year after year because of the strong partnership. Listening to the client helps you in this.
  • Churn happens all the time; collecting user feedback will help with predictions, and if the customer goes away, you certainly know the real reason.

There are some reasons why CSAT is my best friend, and I can spend hours talking about others. That said, sending CSAT manually sucks, and if you’re in a company that doesn’t provide a CRM and Google Workspace is all you have, come with me.

After every meeting, sending a CSAT to all the people in the room depends on two things: time and a good memory. Both of them are rarely found on my desk. That’s the reason I asked Gemini today: “Hey, is there a way, with Apps Script (the programming language of Google Workspace that enables you to do incredible things with their solutions), to send an email after the end of every meeting?”

Get Gabriel Cruz’s stories in your inbox

Join Medium for free to get updates from this writer.

The results changed my day. As a tech student, I’m always searching for ways to automate and program tasks to work better. In this case, I already knew what I needed, that Google technology could help, and I just asked Gemini to do all the job. Let’s look at how to do this.

You can use this prompt to get a similar code by Gemini:

Copy and paste this into Gemini:

"I want to create a Google Apps Script that automatically sends a follow-up CSAT email to external clients after a Google Calendar meeting ends.

Please generate the code based on these requirements:

Trigger: The script should check for meetings that ended within the last 75 minutes.

Filtering: > - It must ignore internal meetings (where all participants share my domain: [INSERT YOUR DOMAIN, e.g., @company.com]).

It must only send emails if there is at least one external guest.

It should ignore 'resource' emails (like meeting rooms).

Anti-Duplication: Use the setTag and getTag methods to mark processed events so clients don't receive the same email twice. Wrap this in a try/catch block to avoid errors on meetings I don't own.

Email Format: Generate a professional HTML email. Use the meeting title in the text and include a call-to-action button linking to my survey: [INSERT YOUR FORM LINK].

Variables: Place all configurations (Domain, Link, Company Name, Team Name) in a single CONFIG object at the top for easy editing.

Please provide the full script and explain how to set up the 'Time-driven' trigger in the Apps Script dashboard.

Gemini wrote this entire code:

/**
* CONFIGURAÇÕES DA AUTOMAÇÃO
* Altere os valores abaixo conforme sua necessidade
*/
const CONFIG = {
DOMINIO_INTERNO: "@seu-dominio.com.br", // Ignore the domain of your own company
LINK_PESQUISA: "https://forms.gle/exemplo", // CSAT link
NOME_EMPRESA: "Sua Empresa", // your company name
TIME_REMETENTE: "Sucesso do Cliente", // your team name
ASSUNTO_EMAIL: "Como foi nossa última conversa?", // e-mail subject
JANELA_MINUTOS: 75, // minutes that the script looks
TAG_CONTROLE: "feedback_enviado"
};

function enviarEmailsPosReuniao() {
const agora = new Date();
const janelaBusca = new Date(agora.getTime() - (CONFIG.JANELA_MINUTOS * 60 * 1000));

const calendario = CalendarApp.getDefaultCalendar();
const eventos = calendario.getEvents(janelaBusca, agora);

eventos.forEach(evento => {
try {
// 1. Filtro: Evento deve ter terminado e não ser de dia inteiro
if (!evento.isAllDayEvent() && evento.getEndTime() <= agora) {

const tituloEvento = evento.getTitle();

// 2. Filtro: Evita envio duplicado checando a etiqueta (tag)
if (evento.getTag(CONFIG.TAG_CONTROLE) === 'true') {
return;
}

const convidados = evento.getGuestList();

// 3. Filtro: Verifica se há convidados externos (clientes)
const temClienteExterno = convidados.some(convidado => {
const email = convidado.getEmail().toLowerCase();
return !email.endsWith(CONFIG.DOMINIO_INTERNO) &&
!email.includes('resource.calendar.google.com') &&
email !== "";
});

if (temClienteExterno) {
convidados.forEach(convidado => {
const emailConvidado = convidado.getEmail().toLowerCase();
const status = convidado.getGuestStatus();

// Pula internos e calendários de sala
if (emailConvidado.endsWith(CONFIG.DOMINIO_INTERNO)) return;
if (emailConvidado.includes('resource.calendar.google.com')) return;

// Envia apenas para quem confirmou ou está convidado
if (status == CalendarApp.GuestStatus.YES || status == CalendarApp.GuestStatus.INVITED) {

const htmlCorpo = `
<div style="font-family: sans-serif; color: #333; line-height: 1.6; max-width: 600px;">
<p>Olá,</p>
<p>Foi um prazer conversar com você na agenda <strong>"${tituloEvento}"</strong>!</p>
<p>Como time de <strong>${CONFIG.TIME_REMETENTE}</strong> da ${CONFIG.NOME_EMPRESA}, valorizamos muito sua opinião.</p>
<p>Poderia responder a essa pesquisa rápida sobre nossa última interação?</p>
<p style="margin-top: 20px;">
<a href="${CONFIG.LINK_PESQUISA}"
style="background-color: #0056b3; color: white; padding: 12px 25px; text-decoration: none; border-radius: 5px; font-weight: bold; display: inline-block;">
Responder Pesquisa
</a>
</p>
<hr style="border: 0; border-top: 1px solid #eee; margin: 20px 0;" />
<p>Atenciosamente,<br>
<strong>${CONFIG.TIME_REMETENTE} | ${CONFIG.NOME_EMPRESA}</strong></p>
</div>
`;

MailApp.sendEmail({
to: emailConvidado,
subject: CONFIG.ASSUNTO_EMAIL,
htmlBody: htmlCorpo
});
}
});

// 4. Marca o evento para não processar novamente
try {
evento.setTag(CONFIG.TAG_CONTROLE, 'true');
Logger.log(`Evento "${tituloEvento}" processado e marcado.`);
} catch (e) {
Logger.log(`Sem permissão para marcar tag no evento "${tituloEvento}".`);
}
}
}
} catch (err) {
Logger.log(`Erro ao processar evento: ${err.message}`);
}
});
}

After getting your code, go to https://script.google.com/ and start a new project. The video shows how to configure the triggers and script:

It’s one way to do it, and no one believes that just sending a CSAT by email guarantees your customer’s answer. This is part of the work; by making sure that your customers have their own channel to talk about their experience, it’s a big deal. In your work, in calls, meetings, or in person, emphasize why this matters to them and for improving the quality of the work.

Take every answer as a gift and enjoy.

Feel free to add me on LinkedIn: https://www.linkedin.com/in/gabriel-m-cruz/

--

--

Gabriel Cruz
Gabriel Cruz

Written by Gabriel Cruz

Customer Success Leader at Scaffold Education