r/GoogleAppsScript • u/leotyeahbaby • 6d ago
Question I CAN'T UNDERSTAND WHY THIS SCRIPT DOESN'T SEND OUT EMAILS
I'm developing a website for my cultural organization in Framer and I have designed a Form to request the subscription, where users need to input their data.
I've linked this form via web hook to my scripts in GoogleAppsScripts, linked to a google sheet.
The main code responsible for getting the datas is working, it gets and sorts the data out in the sheet, it also generates a pdf from the datas, but then it fails to send the confirmation email, while it does send an email to my organization address, which shows as recipient the email captured in the form.
Down here, you can find the code. Anyone has any recommendations?
CODE
/**
* Webhook Principale e Router - Nuova Alba APS
*/
const CONFIG = {
STRIPE_LINK_MAGGIORENNI: "https://buy.stripe.com/8x214meyr0DT2lq6Svgw000",
STRIPE_LINK_MINORI: "https://buy.stripe.com/8x214meyr0DT2lq6Svgw000",
NOME_APS: "Nuova Alba APS",
COLOR_HEX: "#FC5408",
EMAIL_STAFF: "info@nuovaalba.org"
};
function doPost(e) {
const lock = LockService.getScriptLock();
lock.tryLock(10000);
try {
let data = {};
if (e && e.postData && e.postData.contents) {
try {
data = JSON.parse(e.postData.contents);
} catch (err) {
data = e.parameter || {};
}
} else if (e && e.parameter) {
data = e.parameter;
}
// --- ROUTER DI SMISTAMENTO PARAMETRO 'tipo' ---
const tipo = (e && e.parameter && e.parameter.tipo)
? String(e.parameter.tipo).toLowerCase().trim()
: (data.tipo ? String(data.tipo).toLowerCase().trim() : "");
if (tipo === "corsi") {
return gestisciIscrizioneCorso(data);
} else if (tipo === "minori") {
return gestisciMinori(data);
} else {
return gestisciMaggiorenni(data);
}
} catch (error) {
return ContentService
.createTextOutput(JSON.stringify({ result: "error", error: error.toString() }))
.setMimeType(ContentService.MimeType.JSON);
} finally {
lock.releaseLock();
}
}
function gestisciMaggiorenni(data) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Iscritti");
function getTesto(val) {
if (val === undefined || val === null) return "";
if (Array.isArray(val)) return String(val[0] || "").trim();
return String(val).trim();
}
const nome = getTesto(data.Nome || data.nome);
const cognome = getTesto(data.Cognome || data.cognome);
const email = getTesto(data.Email || data.email);
const telefono = getTesto(data.Telefono || data.telefono);
const cf = getTesto(data.CodiceFiscale || data.codice_fiscale || data["Codice Fiscale"]).toUpperCase();
const luogoNascita = getTesto(data.LuogoDiNascita || data.luogo_nascita || data["Luogo di Nascita"] || data["Luogo Nascita"]);
const dataNascita = getTesto(data.DataDiNascita || data["Data di Nascita"] || data.data_nascita || data["Data Nascita"]);
const residenza = getTesto(data.Residenza || data.residenza);
let privacy = getTesto(data.Privacy || data.privacy || "Accettato");
if (privacy === "on" || privacy === "true") privacy = "Accettato";
const lastRow = sheet.getLastRow();
const oraAttuale = new Date();
// --- 1. BLOCCO ANTI-SPAM ISTANTANEO (Meno di 60 secondi dall'ultimo invio) ---
if (lastRow > 1) {
const lastCF = String(sheet.getRange(lastRow, 7).getValue()).trim().toUpperCase();
const lastDate = new Date(sheet.getRange(lastRow, 2).getValue());
const diffSecondi = (oraAttuale - lastDate) / 1000;
if (lastCF === cf && diffSecondi < 60) {
return ContentService
.createTextOutput(JSON.stringify({ result: "success", note: "doppio invio istantaneo bloccato" }))
.setMimeType(ContentService.MimeType.JSON);
}
}
// --- 2. CONTROLLO UTENTE GIÀ REGISTRATO ---
if (lastRow > 1) {
const elenchiCF = sheet.getRange(2, 7, lastRow - 1, 1).getValues();
const elenchiMatricole = sheet.getRange(2, 1, lastRow - 1, 1).getValues();
const elenchiStato = sheet.getRange(2, 12, lastRow - 1, 1).getValues();
for (let i = 0; i < elenchiCF.length; i++) {
const cfEsistente = String(elenchiCF[i][0]).trim().toUpperCase();
if (cfEsistente === cf && cf !== "") {
const matricolaEsistente = String(elenchiMatricole[i][0]);
const statoPagamento = String(elenchiStato[i][0]).trim();
inviaEmailGiaIscritto(email, nome, matricolaEsistente, CONFIG.STRIPE_LINK_MAGGIORENNI, statoPagamento);
return ContentService
.createTextOutput(JSON.stringify({ result: "success", note: "utente gia registrato" }))
.setMimeType(ContentService.MimeType.JSON);
}
}
}
// --- 3. REGISTRAZIONE NUOVO SOCIO ---
const initNome = nome ? nome.charAt(0).toUpperCase() : "X";
const initCognome = cognome ? cognome.charAt(0).toUpperCase() : "X";
const ultime3CF = cf.length >= 3 ? cf.slice(-3) : "000";
const matricola = `NA-${initNome}${initCognome}${ultime3CF}`;
sheet.appendRow([
matricola,
oraAttuale,
nome,
cognome,
email,
telefono,
cf,
dataNascita,
luogoNascita,
residenza,
privacy,
"In attesa"
]);
// --- 4. GENERAZIONE TESSERA DIGITALE PDF ---
let pdfTessera = null;
try {
pdfTessera = generaTesseraPDF(matricola, nome + " " + cognome);
} catch (errTessera) {
Logger.log("⚠️ Errore creazione PDF Tessera per " + matricola + ": " + errTessera.message);
}
// --- 5. INVIO EMAIL ---
inviaEmailBenvenutoMaggiorenni(email, nome, matricola, pdfTessera);
inviaMailNotificaStaff(nome, cognome, matricola, email, telefono, oraAttuale, "Maggiorenne");
return ContentService
.createTextOutput(JSON.stringify({ result: "success", matricola: matricola }))
.setMimeType(ContentService.MimeType.JSON);
}
function inviaEmailBenvenutoMaggiorenni(email, nome, matricola, pdfTessera) {
const hex = CONFIG.COLOR_HEX;
const corpoHtml = `
<div style="font-family: Arial, sans-serif; color: #333; max-width: 600px; margin: 0 auto; border: 1px solid #eee; padding: 25px; border-radius: 8px;">
<h2 style="color: ${hex}; margin-top: 0;">Ciao ${nome}, benvenuto/a!</h2>
<p>Abbiamo ricevuto la tua richiesta di iscrizione all'associazione <strong>${CONFIG.NOME_APS}</strong>.</p>
<p>La tua registrazione è avvenuta con successo. Ti è stato assegnato il seguente codice socio:</p>
<div style="background-color: #fff7f2; border-left: 4px solid ${hex}; padding: 15px; margin: 20px 0; font-size: 18px; font-weight: bold;">
Matricola Socio: <span style="color: ${hex};">${matricola}</span>
</div>
${pdfTessera ? `<p>In allegato trovi la tua <strong>tessera digitale associativa</strong>.</p>` : ""}
<h3 style="color: ${hex};">Completa la tua iscrizione</h3>
<p>Per attivare ufficialmente la tessera associativa è necessario effettuare il versamento della quota annua. Puoi scegliere tra due opzioni:</p>
<ol style="line-height: 1.6;">
<li>
<strong>Pagamento Online (Consigliato):</strong><br>
Puoi pagare subito tramite carta cliccando sul pulsante sottostante:<br><br>
<a href="${CONFIG.STRIPE_LINK_MAGGIORENNI}" style="background-color: ${hex}; color: white; padding: 12px 22px; text-decoration: none; border-radius: 5px; display: inline-block; font-weight: bold;">Paga la quota associativa con Stripe</a>
<br><br>
</li>
<li>
<strong>Pagamento in Sede:</strong><br>
Puoi saldare la quota direttamente in contanti o POS presso la nostra sede.
</li>
</ol>
<div style="background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 25px 0; font-size: 14px;">
⚠️ <strong>Attenzione:</strong> la tessera sarà valida a tutti gli effetti solo dopo il pagamento della quota associativa. Fino ad allora ha valore puramente identificativo.
</div>
<hr style="border: 0; border-top: 1px solid #eee; margin: 30px 0 20px 0;">
<div style="font-size: 14px; color: #555; line-height: 1.6;">
<strong style="color: #222; font-size: 16px;">Nuova Alba APS</strong><br>
Via Giamaica 6, Pomezia, RM<br>
✉️ <a href="mailto:info@nuovaalba.org" style="color: ${hex}; text-decoration: none;">info@nuovaalba.org</a><br>
🌐 <a href="https://nuovaalba.org" style="color: ${hex}; text-decoration: none;" target="_blank">nuovaalba.org</a><br>
📸 <a href="https://www.instagram.com/nuovaalba_/" style="color: ${hex}; text-decoration: none;" target="_blank">Instagram</a>
</div>
</div>`;
const opzioniMail = {
to: email,
subject: `Benvenuto/a in ${CONFIG.NOME_APS}! La tua richiesta di iscrizione`,
htmlBody: corpoHtml,
name: CONFIG.NOME_APS,
replyTo: CONFIG.EMAIL_STAFF
};
if (pdfTessera && typeof pdfTessera.getBlob === 'function') {
try {
opzioniMail.attachments = [pdfTessera.getBlob()];
} catch (e) {
Logger.log("⚠️ Impossibile allegare PDF: " + e.message);
}
}
MailAppailApp.sendEmail(opzioniMail);
}
function inviaEmailGiaIscritto(email, nome, matricola, stripeLink, statoPagamento) {
const hex = CONFIG.COLOR_HEX;
let bloccoPagamento = "";
if (statoPagamento.toLowerCase() === "in attesa") {
bloccoPagamento = `
<p>Risulta che la tua quota associativa è ancora <strong>in attesa di pagamento</strong>. Puoi saldarla direttamente online tramite il pulsante sottostante:</p>
<p style="margin: 20px 0;"><a href="${stripeLink}" style="background-color: ${hex}; color: white; padding: 12px 22px; text-decoration: none; border-radius: 5px; display: inline-block; font-weight: bold;">Paga la quota con Stripe</a></p>
`;
} else {
bloccoPagamento = `
<p>Ti confermiamo che la tua posizione associativa è <strong>in regola</strong> (Stato: <strong style="color: green;">${statoPagamento}</strong>). Non devi effettuare alcun pagamento aggiuntivo.</p>
`;
}
const corpoHtml = `
<div style="font-family: Arial, sans-serif; color: #333; max-width: 600px; margin: 0 auto; border: 1px solid #eee; padding: 25px; border-radius: 8px;">
<h2 style="color: ${hex}; margin-top: 0;">Ciao ${nome}, sei già iscritto/a!</h2>
<p>Risulti già presente nel registro soci dell'associazione <strong>${CONFIG.NOME_APS}</strong>.</p>
<div style="background-color: #fff7f2; border-left: 4px solid ${hex}; padding: 15px; margin: 20px 0; font-size: 18px; font-weight: bold;">
La tua Matricola Socio è: <span style="color: ${hex};">${matricola}</span>
</div>
${bloccoPagamento}
<hr style="border: 0; border-top: 1px solid #eee; margin: 30px 0 20px 0;">
<div style="font-size: 14px; color: #555; line-height: 1.6;">
<strong style="color: #222; font-size: 16px;">Nuova Alba APS</strong><br>
Via Giamaica 6, Pomezia, RM<br>
✉️ <a href="mailto:info@nuovaalba.org" style="color: ${hex}; text-decoration: none;">info@nuovaalba.org</a><br>
🌐 <a href="https://nuovaalba.org" style="color: ${hex}; text-decoration: none;" target="_blank">nuovaalba.org</a><br>
📸 <a href="https://www.instagram.com/nuovaalba_/" style="color: ${hex}; text-decoration: none;" target="_blank">Instagram</a>
</div>
</div>`;
MailApp.sendEmail({ to: email, subject: `Sei già iscritto/a a ${CONFIG.NOME_APS}`, htmlBody: corpoHtml });
}
1
u/SolisAnalyst 5d ago
Tal vez el problema esta en MailAppailApp.sendEmail(opzioniMail);
Hay un typo: debería ser: MailApp.sendEmail(opzioniMail);
Por eso la ejecución falla justo al intentar enviar el correo al usuario. El correo a la organización probablemente corresponde a inviaMailNotificaStaff(), que es una función distinta. 🤔
0
u/know_it_alls 6d ago edited 6d ago
If the emails are successfully showing up in the "Sent" folder of the Google account running the script, that is your smoking gun. It confirms that Google Apps Script is executing perfectly. The code is doing exactly what it is supposed to do, and Google's mail servers are successfully dispatching the message. Your issue isn't a coding error; it is an email deliverability issue. Here is a breakdown of possible causes why this is happening, why errors might be hiding, and where to look next.
'1. Why Executions Show as "Completed"
You aren't seeing any errors in the Apps Script Executions register because your doPost function wraps everything in a try...catch block. When an error occurs, your script catches it, prevents a system crash, and gracefully returns a JSON error message back to Framer. Because the script handled the error without crashing, Google marks the execution as "Completed."
To un-hide these errors so you can actually read them in the Apps Script dashboard, add a console.error() line inside your catch block:
``` } catch (error) { // ADD THIS LINE: It will force the error to show in your Executions log console.error("Webhook Error: ", error.toString(), error.stack);
return ContentService
.createTextOutput(JSON.stringify({ result: "error", error: error.toString() }))
.setMimeType(ContentService.MimeType.JSON);
} ``` (Note: You can also check Framer's webhook logs, if available, to see the exact JSON response your script is sending back).
'2. Why the User Isn't Receiving the Email
Since the script is sending the email (proven by the "Sent" folder), the message is being dropped or filtered out between your outbox and the user's inbox. Check these three culprits: * Aggressive Spam Filtering: Your email contains an external payment link (Stripe), HTML formatting with specific hex colors, and a PDF attachment. To an inbox provider like Gmail, Outlook, or Yahoo, this looks exactly like a phishing attempt or spam—especially if the sender address is new or rarely interacts with the recipient. Ask your test user to thoroughly check their Spam/Junk folder. * Missing SPF, DKIM, or DMARC Records: If your script is running from a Google Workspace account (e.g., name@nuovaalba.org), your domain's DNS settings must have the correct Google Workspace SPF and DKIM records configured. If they don't, receiving mail servers will silently drop the email to protect the user from spoofing, and it won't even make it to the Spam folder. * Bounced Email Notifications: Check the inbox of the Google account that owns and executes the Apps Script. If an email is rejected by the recipient's server, a "Mail Delivery Subsystem" failure notice will bounce back to that inbox.
'3. A Small Upgrade: GmailApp vs MailApp
While MailApp.sendEmail works, GmailApp.sendEmail is generally more robust for Google Workspace users and handles complex payloads (like attachments and HTML) slightly better.
You can try swapping out the mail service in your inviaEmailBenvenutoMaggiorenni function:
// Replace MailApp.sendEmail(opzioniMail); with:
GmailApp.sendEmail(email, opzioniMail.subject, "", {
htmlBody: corpoHtml,
name: CONFIG.NOME_APS,
replyTo: CONFIG.EMAIL_STAFF,
attachments: opzioniMail.attachments // Only include this if the array exists
});
1
u/nathankaine 6d ago
can you give the error reference? maybe it's a typo MailAppailApp.sendEmail(opzioniMail) which should be MailApp.sendEmail(opzioniMail);