Contact form Web App with Gmail notification. Validates user input server-side and sends email via GmailApp.sendEmail.
Built from apps-script-fleet template.
doGet → Serve form.html as Web App
→ User submits form via google.script.run
→ submitForm validates input (form-validator.ts)
→ Build email (mail-builder.ts)
→ Send via GmailApp.sendEmail
→ Return { status, message } to client
| Environment | Link |
|---|---|
| dev | form-mailer-dev |
| prod | form-mailer-prod |
Open Google Apps Script → create a new project → copy the scriptId from the project URL.
Set the scriptId in .clasp-dev.json and .clasp-prod.json:
{ "scriptId": "YOUR_SCRIPT_ID", "rootDir": "dist" }Change ADMIN_EMAIL in src/index.ts to the email address that should receive contact form submissions.
Deploy via the Apps Script editor or clasp deploy with the following settings:
| Setting | Value |
|---|---|
| Execute as | Me |
| Who has access | Anyone |
The script runs as the deploying user's account, so Gmail sends from that account.
src/
├── index.ts # GAS entry points: doGet, submitForm (no export keyword)
├── form-validator.ts # Pure validation logic
├── mail-builder.ts # HTML email builder with XSS escaping
└── form.html # Contact form UI (google.script.run AJAX pattern)
test/
├── form-validator.test.ts
└── mail-builder.test.ts
| Command | Description |
|---|---|
pnpm run check |
lint + lint:css + lint:html + typecheck + test (all checks) |
pnpm run build |
Bundle TypeScript + copy assets to dist/ |
pnpm run test |
Jest with coverage |
pnpm run test -- --watch |
Jest watch mode |
pnpm run deploy |
check → build → deploy to dev |
pnpm run deploy:prod |
check → build → deploy to production |
CI runs on every push and PR. CD deploys on merge to dev or main — configured via GitHub Actions secrets/variables per environment. See apps-script-fleet docs for details.
- Functions in
src/index.tsmust not have theexportkeyword — the GAS runtime does not support ES module syntax src/index.tsis excluded from test coverage (GAS globals cannot run in Node.js)- Coverage threshold: 80% for all metrics (configurable in
jest.config.json)