Every side project or simple client build ends up needing the exact same thing: a dead-simple way to send transactional emails.
Whether it is a contact form on a static site, a failure alert from a cron job, or a notification ping from a small internal tool, you almost always need an API endpoint to fire off an email.
The problem is the modern email API landscape for small projects has gotten annoying:
- Big providers now want credit cards upfront just to touch their free tier.
- Setup involves domain DNS verifications, DKIM, SPF records, and waiting on sandbox approvals.
- You end up managing API keys, dependencies, and complex SDKs just to send 5 emails a day.
If you already have a Google account, you can build and host your own secure REST email API for free in about 80 lines of Google Apps Script. No credit card, no server maintenance, and no domain DNS config required.
Here is what it is, how it works, what Google gives you for free, and how to set it up.
What It Is
It is a lightweight Google Apps Script deployed as a serverless Web App that exposes an authenticated HTTP POST endpoint.
When your backend or serverless function sends a JSON payload to the endpoint, the script validates a bearer token stored in your Google Script Properties and sends the email straight from your Gmail or Google Workspace account using Google's native MailApp service.
It supports:
- Simple JSON over HTTP
POST - Token-based bearer authentication
- Plain text and HTML emails
- Custom sender display names
- CC, BCC, and Reply-To headers
- Returning your remaining daily recipient quota directly in the API response
What You Get for Free
Google Apps Script runs entirely on Google's infrastructure at zero cost. You do not need to supply billing details or provision a cloud instance.
Google provides generous daily email sending quotas based on your account type:
| Account Type | Daily Email Recipient Quota |
|---|---|
| Personal Google Account (@gmail.com) | 100 recipients / day |
| Google Workspace (formerly G Suite) | 1,500 recipients / day |
For a personal project, portfolio contact form, or dev monitoring script, 100 free transactional emails every single day is more than enough. If you run it on an existing Workspace account, 1,500 per day covers plenty of early-stage SaaS applications or internal company tools.
Even better: because the emails are sent directly through Gmail under your authenticated Google account, inbox deliverability is solid right out of the box without configuring custom SMTP servers.
How It Works
The entire API is contained in a single Code.gs file.
Here is the breakdown of the logic:
1. Generating a Secure Token
We store a unique token inside PropertiesService.getScriptProperties(). This acts like an environment variable that persists across executions and is never exposed in the source code or client requests:
const TOKEN_KEY = 'AUTH_TOKEN'
function generateToken() {
const newToken = Utilities.getUuid() + '-' + Utilities.getUuid()
PropertiesService.getScriptProperties().setProperty(TOKEN_KEY, newToken)
Logger.log('Your Auth Token: ' + newToken)
}
Running this function once generates a secure, randomized token and saves it in the script storage.
2. Handling Incoming POST Requests
The entry point for web apps in Google Apps Script is doPost(e). The script parses the incoming JSON, validates the token against the stored property, checks required fields, and sends the message:
function doPost(e) {
try {
if (!e.postData || !e.postData.contents) {
return jsonResponse({ success: false, error: 'Missing request body' }, 400)
}
const data = JSON.parse(e.postData.contents)
// Auth check
const storedToken = PropertiesService.getScriptProperties().getProperty(TOKEN_KEY)
if (!storedToken || data.token !== storedToken) {
return jsonResponse({ success: false, error: 'Invalid or missing auth token' }, 401)
}
// Required fields check
if (!data.to || !data.subject || (!data.body && !data.htmlBody)) {
return jsonResponse({
success: false,
error: 'Missing required fields: to, subject, and body (or htmlBody)'
}, 400)
}
// Build optional parameters
const options = {}
if (data.htmlBody) options.htmlBody = data.htmlBody
if (data.cc) options.cc = data.cc
if (data.bcc) options.bcc = data.bcc
if (data.replyTo) options.replyTo = data.replyTo
if (data.name) options.name = data.name
// Send the email via Gmail
MailApp.sendEmail(data.to, data.subject, data.body || '', options)
// Return success and remaining quota
return jsonResponse({
success: true,
message: 'Email sent successfully',
remainingQuota: MailApp.getRemainingDailyQuota()
})
} catch (err) {
return jsonResponse({
success: false,
error: err.message
}, 500)
}
}
function jsonResponse(obj) {
return ContentService
.createTextOutput(JSON.stringify(obj, null, 2))
.setMimeType(ContentService.MimeType.JSON)
}
Notice MailApp.getRemainingDailyQuota(). Returning this in the response lets your calling app monitor your usage without guessing where you stand against Google limits.
5-Minute Setup Guide
Setting this up takes only a few steps:
1. Create the Script
- Head over to script.google.com and click New Project.
- Replace whatever is in the editor with the
Code.gsscript above. - Name your project (e.g.
Gmail Email API).
2. Generate Your Secret Token
- In the Apps Script toolbar, select
generateTokenfrom the function dropdown. - Click Run.
- Google will ask you to review permissions. Authorize the script to access your Gmail account.
- Open the Execution log (View → Execution log) and copy the generated token string. Store this securely in your
.envfile.
3. Deploy as a Web App
- Click Deploy → New deployment (top right).
- Choose Web app as the deployment type.
- Configure the options:
-
Description:
Production v1 -
Execute as:
Me (your email address) -
Who has access:
Anyone
-
Description:
- Click Deploy and copy your Web app URL. It will look like this:
https://script.google.com/macros/s/AKfycbx.../exec
That is your live REST API endpoint.
Sending an Email
Once deployed, you can trigger emails from any language or framework using a standard HTTP POST request.
cURL
curl -X POST "https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec" \
-H "Content-Type: application/json" \
-d '{
"token": "YOUR_AUTH_TOKEN",
"to": "dev@example.com",
"name": "App Monitor",
"subject": "System Status Update",
"body": "Daily backup completed with zero errors.",
"htmlBody": "<h3>System Status Update</h3><p>Daily backup completed with <strong>zero errors</strong>.</p>"
}'
Node.js / Next.js / Astro
const API_URL = process.env.GMAIL_API_URL
const API_TOKEN = process.env.GMAIL_API_TOKEN
async function sendNotification({ to, subject, body, htmlBody }) {
const res = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: API_TOKEN,
to,
subject,
body,
htmlBody,
name: 'My Service'
})
})
const data = await res.json()
if (!data.success) {
throw new Error(data.error || 'Failed to send')
}
console.log(`Sent! Remaining quota today: ${data.remainingQuota}`)
return data
}
Python
import os
import requests
def send_email(to, subject, body):
res = requests.post(
os.environ["GMAIL_API_URL"],
json={
"token": os.environ["GMAIL_API_TOKEN"],
"to": to,
"subject": subject,
"body": body,
},
timeout=15,
)
res.raise_for_status()
return res.json()
Practical Use Cases
This setup is ideal when you want reliable transactional delivery without taking on extra SaaS bills or infrastructure overhead:
- Static Site Contact Forms: If you run a static site on Netlify, Vercel, Cloudflare Pages, or GitHub Pages, route your form submission to a serverless backend function that hits your Apps Script endpoint.
- Build and Deployment Alerts: Hook it into GitHub Actions, GitLab CI, or custom webhooks to notify your team when a scheduled build or release succeeds or fails.
- Server Monitoring & Heartbeats: Drop a quick Python script or curl check on a cheap VPS or Raspberry Pi. If a service drops offline or disk usage hits 90%, fire an alert to your personal inbox.
- Internal Micro-Tools & Staging Environments: Great for dev and staging environments where you need real emails to test workflows without burning paid quotas on your production email provider.
- Database Backup Reports: Trigger a daily status summary from your Postgres backup scripts.
Limitations and Gotchas to Keep in Mind
A quick reminder on practical constraints:
-
Security rule: Always keep your
AUTH_TOKENon a server or behind an edge function. Never put the token inside client-side JavaScript or a public repo. Anyone who gets that token can send email through your account. -
Attachments: This basic script handles text and HTML bodies. If you need PDF invoices or zip attachments, you would need to extend the script to accept base64-encoded file data and pass them as blobs to
MailApp. -
HTTP Status Codes in Apps Script: Google Apps Script web apps return HTTP 200 or 302 redirects for most
ContentServicetext outputs. The script passes error status codes injsonResponse, but you should always inspect thesuccess: true/falsekey in the returned JSON body rather than relying strictly on the HTTP status code. - Bulk Marketing: Do not use this for newsletters or cold outreach. Google will throttle or flag accounts that violate standard Gmail usage policies. Keep this strictly for transactional notifications and alerts.
Wrap Up
If you are sick of configuring third-party email providers for tiny projects, Google Apps Script gives you a dependable, completely free alternative that you can roll out in five minutes.
Check out the full repository and documentation here:
👉 swindon/gmail-email-api on GitHub
Feel free to star it, fork it, or adapt the payload to your own workflow.
Top comments (0)