Featured image of post Self-Hosted Email Sending: Use Gmail API to Send Emails for Free from Your VPS

Self-Hosted Email Sending: Use Gmail API to Send Emails for Free from Your VPS

Step-by-step guide to set up Gmail API for sending transactional emails from your VPS — system alerts, service notifications, password resets, all for free

Introduction

A VPS isn’t truly “alive” until it can send emails. System alert notifications, SSL certificate expiration reminders, backup failure reports, user registration verifications — all require email delivery capability.

Services like SendGrid, Mailgun, and Resend exist, but they either have tight limits or require a credit card before you can use them. The Gmail API is completely free: every Google account can send up to 100 emails per day, which is more than enough for personal VPS operations.

This guide walks you through setting up Gmail API email sending from scratch, supporting both Python and Shell scripts, so you can ditch third-party email service bills for good.


1. Why Choose Gmail API?

SolutionCostDaily LimitSetup Complexity
Gmail APIFree100/day⭐⭐
SendGrid FreeFree100/day⭐⭐
Mailgun FreeFree100/day⭐⭐⭐
Self-hosted SMTPFreeUnlimited⭐⭐⭐⭐⭐
Amazon SESPay-as-you-go200/day (Sandbox)⭐⭐⭐

Gmail API advantages:

  • Zero cost: No credit card required — just use your Google account
  • High deliverability: Gmail’s IP reputation keeps your emails out of spam folders
  • Built-in labeling: Emails automatically categorized in Gmail for easy management
  • Zero maintenance: Google handles the infrastructure — you just send

⚠️ Note: If you need to send bulk marketing emails (like newsletters), Gmail API is not the right choice — don’t use a personal Gmail account for mass sending. This guide covers operational notifications and transactional emails.


2. Prerequisites: Setting Up a Google Cloud Project

2.1 Create a Project

  1. Open Google Cloud Console
  2. Click the project dropdown at the top → New Project
  3. Enter project name: VPS Email Sender (any name works)
  4. Wait for creation and ensure the project is selected

2.2 Enable the Gmail API

  1. Go to APIs & Services → Library
  2. Search for Gmail API
  3. Click it → Enable
  1. Go to APIs & Services → OAuth consent screen
  2. Choose External → Create
  3. Fill in:
    • App name: VPS Email Sender
    • User support email: Your email
    • Developer contact: Your email
  4. Skip the Scopes step (click “Save and Continue”)
  5. Test users: Add your Gmail address
  6. Click Back to DashboardPublish App

2.4 Create Credentials

  1. Go to APIs & Services → Credentials
  2. Click Create Credentials → OAuth client ID
  3. Application type: Desktop application
  4. Name: VPS Email Client
  5. After creation, click Download JSON and save as credentials.json

3. Install Dependencies & Send Your First Email

3.1 Server Setup

Install Python dependencies on your VPS:

pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib

3.2 Authorization Script

Create an authorization script, run it once on your local machine, then transfer the token to your VPS:

# quickstart.py
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow

SCOPES = ['https://www.googleapis.com/auth/gmail.send']

def authenticate():
    creds = None
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        with open('token.json', 'w') as token:
            token.write(creds.to_json())
    return creds

if __name__ == '__main__':
    authenticate()
    print("✅ Authorization successful! token.json generated")

Run this on your local machine (it needs a browser):

python3 quickstart.py

A browser window will open asking you to log in to Google. After authorization, token.json will be generated.

💡 Pro tip: If your VPS has no GUI, run the auth locally, then copy token.json and credentials.json to your VPS using scp.

3.3 Send Email Script

After uploading token.json to your VPS, use this script:

# send_email.py
import base64
import os
import sys
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from email.mime.text import MIMEText

SCOPES = ['https://www.googleapis.com/auth/gmail.send']

def get_service():
    creds = None
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            raise Exception("Token invalid, please re-authorize")
    return build('gmail', 'v1', credentials=creds)

def send_email(to, subject, body):
    service = get_service()
    message = MIMEText(body, 'plain', 'utf-8')
    message['To'] = to
    message['Subject'] = subject
    raw = base64.urlsafe_b64encode(message.as_bytes()).decode()
    
    try:
        result = service.users().messages().send(
            userId='me', body={'raw': raw}
        ).execute()
        print(f"✅ Sent! Message ID: {result['id']}")
        return True
    except HttpError as error:
        print(f"❌ Failed: {error}")
        return False

if __name__ == '__main__':
    to = sys.argv[1] if len(sys.argv) > 1 else input("To: ")
    subject = sys.argv[2] if len(sys.argv) > 2 else "Test Email"
    body = sys.argv[3] if len(sys.argv) > 3 else "This is a test email sent via Gmail API."
    send_email(to, subject, body)

Test it:

python3 send_email.py "your-email@gmail.com" "Gmail API Test" "Hello from my VPS! 🎉"

4. Advanced: Shell Script Wrapper

For system scripts and crontab tasks, create a simple shell wrapper:

#!/bin/bash
# send_email.sh — Send email via Gmail API
# Usage: ./send_email.sh "recipient" "subject" "body"

TO="$1"
SUBJECT="$2"
BODY="$3"

python3 -c "
import sys
sys.path.insert(0, '/opt/email-sender')
from send_email import send_email
send_email('$TO', '$SUBJECT', '$BODY')
"

Add it to crontab for system monitoring:

# Check SSL certificate daily at 8 AM
0 8 * * * /opt/scripts/check_ssl.sh || /opt/email-sender/send_email.sh "admin@example.com" "⚠️ SSL Certificate Expiring Soon" "$(date): Certificate check failed, please investigate"

# Monthly VPS summary on the 1st
0 9 1 * * /opt/scripts/summary.sh && /opt/email-sender/send_email.sh "admin@example.com" "📊 Monthly VPS Report" "$(cat /tmp/monthly_report.txt)"

5. Practical Examples: System Monitoring Integration

5.1 Disk Space Alert

#!/bin/bash
# disk_alert.sh
THRESHOLD=85
USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')

if [ "$USAGE" -ge "$THRESHOLD" ]; then
    /opt/email-sender/send_email.sh \
        "admin@example.com" \
        "🚨 Disk Space Alert: ${USAGE}% Used" \
        "Server: $(hostname)\nTime: $(date)\nDisk Usage: ${USAGE}%\n\n$(df -h)"
fi

Add to crontab for hourly checks:

0 * * * * /opt/scripts/disk_alert.sh

5.2 Docker Container Restart Notification

#!/bin/bash
# docker_restart_notify.sh
RESTARTED=$(docker ps -a --filter "status=exited" --format "{{.Names}} {{.Status}}" | head -5)
if [ -n "$RESTARTED" ]; then
    /opt/email-sender/send_email.sh \
        "admin@example.com" \
        "🔄 Docker Container Crashed" \
        "These containers have exited:\n\n$RESTARTED\n\nPlease investigate."
fi

5.3 Nginx Error Log Monitor

#!/bin/bash
# nginx_error_monitor.sh
ERRORS=$(tail -n 50 /var/log/nginx/error.log | grep -iE "error|critical|alert" | tail -10)
if [ -n "$ERRORS" ]; then
    /opt/email-sender/send_email.sh \
        "admin@example.com" \
        "⚠️ Nginx Errors Detected" \
        "Recent Nginx error log entries:\n\n$ERRORS"
fi

6. Security & Optimization

6.1 Token Management

  • token.json is the key to your email — never commit it to GitHub
  • Recommended path: /opt/email-sender/token.json
  • Set permissions: chmod 600 /opt/email-sender/token.json
  • Use git-crypt or sops for encrypted storage

6.2 Rate Limiting

Gmail API quotas:

  • 100 messages per day (free account)
  • Maximum 10 messages per 100 seconds
  • Exceeding limits returns 429 Too Many Requests

Add retry logic to your script:

import time
from googleapiclient.errors import HttpError

def send_with_retry(service, body, max_retries=3):
    for attempt in range(max_retries):
        try:
            return service.users().messages().send(
                userId='me', body=body
            ).execute()
        except HttpError as e:
            if e.resp.status == 429 and attempt < max_retries - 1:
                wait = 2 ** attempt
                print(f"⚠️ Rate limited, waiting {wait}s...")
                time.sleep(wait)
            else:
                raise
    return None

6.3 Multi-Account Rotation

If you need more than 100 emails/day, prepare multiple Gmail accounts and rotate:

TOKENS = ['token1.json', 'token2.json', 'token3.json']
current = 0

def get_next_token():
    global current
    token = TOKENS[current % len(TOKENS)]
    current += 1
    return token

7. Troubleshooting

Token has been expired or revoked

Cause: Token expired or was revoked Fix: Delete old token.json and re-run authorization

Access blocked: XXXXXXXXX

Cause: Google security check triggered Fix: Ensure OAuth app is published (set to “Production”) and test user is added

Recipient address rejected

Cause: Invalid or non-existent recipient address Fix: Verify the recipient email is correct

❌ Email goes to Gmail Spam

Cause: IP reputation or content flagged Fix:

  • Add SPF/DKIM records (if sending from a custom domain)
  • Avoid excessive URLs
  • Don’t use trigger words (“urgent”, “free”, etc.)

Summary

Using the Gmail API to send emails is one of the most cost-effective and reliable solutions for VPS operations. The entire setup requires zero cost and about 15 minutes of configuration — once done, your server’s monitoring and notification system is fully operational.

Key takeaways:

  • ✅ Free: $0 cost, 100 emails/day is plenty for personal operations
  • ✅ Efficient: Gmail’s deliverability far exceeds self-hosted mail servers
  • ✅ Simple: One Python script + one token file
  • ✅ Scalable: Multi-account rotation, HTML templates, custom labeling

Start today — ditch paid email services and give your VPS its own email sending capability!


This article is part of the “Cloud Savings” series. Follow selfvps.net for more self-hosting tips.

📺 看视频版教程 → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials