Dashboard
Business Overview
Business Settings
Email Template Editor
Available Placeholders
{{Business Name}}
Live Preview
Start typing to see a preview…
Scheduler
Daily Automation
Disabled
(Europe/Madrid)
Last Run: Never
⚠️ The scheduler works while this browser tab is open. For true 24/7 scheduling, set a time-based trigger in your Google Apps Script.
Run Log
| Timestamp | Type | Rows Processed | Sent | Skipped | Status |
|---|
No runs recorded yet.
Google Apps Scripts
Copy this single unified script into your Google Sheet's Apps Script editor (Extensions → Apps Script). It handles the full pipeline: read emails → fill sheet → send approval emails. Deploy as a Web App.
🚀 Setup Instructions
- Open your Google Sheet →
Extensions → Apps Script - Delete ALL existing code, paste the script below
- Click 💾 Save (Ctrl+S)
- Select
runFullAutomationfrom the dropdown → click ▶ Run - Grant permissions when prompted (Review → Allow)
- Click Deploy → New deployment → Web app → Execute as: Me → Access: Anyone → Deploy
- Copy the Web App URL and paste it in Settings → Google Sheet tab AND Settings → General (Sender Script)
- ⚠️ IMPORTANT: Every time you update the script, you must create a New deployment for changes to take effect
- Set a daily trigger: Triggers → + Add Trigger →
runFullAutomation→ Time-driven → Day timer
⚡ Automation Script — Send Emails Only
Script that fetches Sheet Data, sends Approval Emails based on Date, and handles Test Sends.
automation.gs
// ============================================================
// AUTOMATION SCRIPT — SEND ONLY
// Paste in: Google Sheet > Extensions > Apps Script
// IMPORTANT: After pasting, Deploy > New deployment
// ============================================================
var CONFIG = {
MY_EMAIL: "info@astrahub.uk", // Optional: Used for Reply-To
SHEET_NAME: "Sheet1",
DATE_FIELD: "Día",
SEND_TO: "info@goastra.es", // Who receives the confirmation emails
TIMEZONE: "Europe/Madrid"
};
var LOG = [];
function log(msg) { Logger.log(msg); LOG.push(msg); }
function getTodayMadrid() {
return Utilities.formatDate(new Date(), CONFIG.TIMEZONE, "yyyy-MM-dd");
}
function normalizeDate(value) {
if (!value) return "";
if (value instanceof Date) {
return Utilities.formatDate(value, CONFIG.TIMEZONE, "yyyy-MM-dd");
}
var s = value.toString().trim();
if (s.indexOf("T") !== -1) {
try {
return Utilities.formatDate(new Date(s), CONFIG.TIMEZONE, "yyyy-MM-dd");
} catch(e) {}
}
return s.substring(0, 10);
}
// ── SEND APPROVAL EMAILS ─────────────────────────────────────
function sendApprovalEmails() {
var res = {today:"", total:0, matched:0, sent:0, errors:[]};
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.SHEET_NAME);
if (!sheet) {
res.errors.push("Sheet not found");
return res;
}
var today = getTodayMadrid();
res.today = today;
log("Today (Madrid): " + today);
var data = sheet.getDataRange().getValues();
res.total = Math.max(0, data.length - 1);
log("Data rows: " + res.total);
if (data.length < 2) return res;
var hdrs = data[0].map(function(h){ return h.toString().trim(); });
// Find Día column (accent-safe)
var diaIdx = -1;
for (var h=0; h<hdrs.length; h++) {
var norm = hdrs[h].toLowerCase().replace(/[íi]/g,"i").replace(/[áa]/g,"a");
if (norm === "dia") { diaIdx = h; break; }
}
if (diaIdx === -1) {
log("ERROR: 'Día' column not found.");
res.errors.push("Día column missing");
return res;
}
var nombreIdx=-1, horaIdx=-1, servicioIdx=-1, numIdx=-1;
for (var h2=0; h2<hdrs.length; h2++) {
var lo = hdrs[h2].toLowerCase();
if (lo==="nombre") nombreIdx=h2;
if (lo==="hora") horaIdx=h2;
if (lo==="servicio") servicioIdx=h2;
if (lo.replace(/[úu]/g,"u")==="numero") numIdx=h2;
}
for (var i=1; i<data.length; i++) {
var row = data[i];
var rawDia = row[diaIdx];
var rowDia = normalizeDate(rawDia);
if (rowDia !== today) { continue; }
res.matched++;
var nombre = nombreIdx!==-1 ? row[nombreIdx] : "";
var hora = horaIdx!==-1 ? row[horaIdx] : "";
var servicio = servicioIdx!==-1 ? row[servicioIdx] : "";
var numero = numIdx!==-1 ? row[numIdx] : "";
var recipient = CONFIG.SEND_TO;
var subject = "✅ Reserva — " + servicio + " · " + today;
var emailBody =
"Hola " + nombre + ",\n\n" +
"Reserva confirmada:\n\n" +
" • Número: " + numero + "\n" +
" • Servicio: " + servicio + "\n" +
" • Día: " + rowDia + "\n" +
" • Hora: " + hora + "\n\n" +
"Un saludo,\nAstra";
try {
GmailApp.sendEmail(recipient, subject, emailBody, {
name: "Astra",
replyTo: CONFIG.MY_EMAIL
});
res.sent++;
log("Sent → " + recipient);
} catch(e) {
log("ERROR sending: " + e.message);
res.errors.push("Send error: " + e.message);
}
}
log("Matched: " + res.matched + " Sent: " + res.sent);
return res;
}
function runFullAutomation() {
LOG = [];
log("=== SEND PROCESS START ===");
var outbound = sendApprovalEmails();
log("=== END ===");
return { outbound: outbound, log: LOG };
}
// ── GET ENDPOINT FOR PWA (DATA & RUN AUTOMATION) ─────────────
function doGet(e) {
var action = e && e.parameter && e.parameter.action ? e.parameter.action : "status";
// Trigger automation via ?action=run
if (action === "run") {
var r = runFullAutomation();
return ContentService.createTextOutput(JSON.stringify({
success: true,
outbound: r.outbound,
log: r.log,
time: new Date().toISOString()
})).setMimeType(ContentService.MimeType.JSON);
}
// Supply data to PWA via ?action=data
if (action === "data") {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.SHEET_NAME);
if (!sheet) return ContentService.createTextOutput("[]").setMimeType(ContentService.MimeType.JSON);
var data = sheet.getDataRange().getValues();
if (data.length < 2) return ContentService.createTextOutput("[]").setMimeType(ContentService.MimeType.JSON);
var hdrs = data[0].map(function(h){ return h.toString().trim(); });
var rows = [];
for (var i=1; i<data.length; i++) {
var obj = {};
hdrs.forEach(function(hdr,j) {
var v = data[i][j];
if (v instanceof Date) v = Utilities.formatDate(v, CONFIG.TIMEZONE, "yyyy-MM-dd");
obj[hdr] = v;
});
rows.push(obj);
}
return ContentService.createTextOutput(JSON.stringify(rows)).setMimeType(ContentService.MimeType.JSON);
}
return ContentService.createTextOutput(JSON.stringify({
status: "ok", today: getTodayMadrid()
})).setMimeType(ContentService.MimeType.JSON);
}
// ── POST ENDPOINT TO ALLOW 'TEST SEND' PWA REQUESTS ──────────
function doPost(e) {
try {
var p = JSON.parse(e.postData.contents);
var to = p.to || CONFIG.SEND_TO;
var subject = p.subject || "Test Subject";
var body = p.body || "Test Body";
var replyTo = p.from || CONFIG.MY_EMAIL;
var fromName = p.fromName || "Astra";
GmailApp.sendEmail(to, subject, body, {
name: fromName,
replyTo: replyTo
});
return ContentService.createTextOutput(JSON.stringify({
success: true,
message: "Email sent"
})).setMimeType(ContentService.MimeType.JSON);
} catch(err) {
return ContentService.createTextOutput(JSON.stringify({
success: false,
error: err.message
})).setMimeType(ContentService.MimeType.JSON);
}
}