Skip to main content

Chapter 20 - Sending Emails, Texts, and Push Notifications (JavaScript)

Here's a JavaScript-flavoured version of the same concepts, with small JS/Node examples for each idea.


Overview and safety warning

Same rules apply in JavaScript:

  • Use a separate Gmail account for scripts.
  • Always do a dry run first: console.log() what would be sent before actually sending.

Gmail API with googleapis

In JavaScript, use the official googleapis package (same API that EZGmail wraps in Python).

npm install googleapis

Enabling the API and init

Setup steps are the same as Python (Google Cloud Console, enable Gmail API, OAuth consent screen with https://mail.google.com/ scope, download credentials).

const { google } = require("googleapis");
const fs = require("fs");

const credentials = JSON.parse(fs.readFileSync("credentials.json", "utf-8"));
const { client_id, client_secret, redirect_uris } = credentials.installed;

const oAuth2Client = new google.auth.OAuth2(
client_id,
client_secret,
redirect_uris[0]
);

// Load token (after initial auth flow)
const token = JSON.parse(fs.readFileSync("token.json", "utf-8"));
oAuth2Client.setCredentials(token);

const gmail = google.gmail({ version: "v1", auth: oAuth2Client });

Sending mail

Gmail API requires base64url-encoded RFC 2822 messages:

function createMessage(to, subject, body) {
const message = [
`To: ${to}`,
`Subject: ${subject}`,
"",
body,
].join("\n");

return Buffer.from(message).toString("base64url");
}

async function sendEmail(to, subject, body) {
const raw = createMessage(to, subject, body);
const res = await gmail.users.messages.send({
userId: "me",
requestBody: { raw },
});
console.log("Sent:", res.data.id);
}

sendEmail("recipient@example.com", "Subject line", "Body of the email");

For attachments, use the nodemailer package to build MIME messages, then send via Gmail API.

Reading mail: threads and messages

List unread threads:

async function getUnreadThreads(maxResults = 25) {
const res = await gmail.users.threads.list({
userId: "me",
q: "is:unread",
maxResults,
});
return res.data.threads || [];
}

Get thread details with messages:

async function getThread(threadId) {
const res = await gmail.users.threads.get({
userId: "me",
id: threadId,
});
return res.data;
}

// Usage
const threads = await getUnreadThreads();
if (threads.length > 0) {
const thread = await getThread(threads[0].id);
const msg = thread.messages[0];

// Headers are in msg.payload.headers array
const subject = msg.payload.headers.find((h) => h.name === "Subject")?.value;
const from = msg.payload.headers.find((h) => h.name === "From")?.value;
console.log(subject, from);

// Body (plain text part)
const body = Buffer.from(
msg.payload.body.data || "",
"base64"
).toString("utf-8");
console.log(body);
}

Searching mail

Use the same Gmail search operators via the q parameter:

async function searchMail(query, maxResults = 25) {
const res = await gmail.users.threads.list({
userId: "me",
q: query,
maxResults,
});
return res.data.threads || [];
}

// Examples
const results = await searchMail("RoboCop");
const withAttachments = await searchMail("has:attachment");
const fromAlice = await searchMail("from:alice@example.com");

Downloading attachments

async function downloadAttachment(messageId, attachmentId, filename) {
const res = await gmail.users.messages.attachments.get({
userId: "me",
messageId,
id: attachmentId,
});

const data = Buffer.from(res.data.data, "base64");
fs.writeFileSync(filename, data);
console.log(`Downloaded: ${filename}`);
}

Simpler alternative: nodemailer

For sending only (not reading), nodemailer with Gmail is simpler:

npm install nodemailer
const nodemailer = require("nodemailer");

const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: "your-script-account@gmail.com",
pass: "your-app-password", // use App Password, not regular password
},
});

async function sendEmail(to, subject, body) {
await transporter.sendMail({
from: "your-script-account@gmail.com",
to,
subject,
text: body,
// attachments: [{ filename: 'file.jpg', path: './file.jpg' }],
});
console.log("Email sent!");
}

sendEmail("recipient@example.com", "Subject line", "Body of the email");

With cc/bcc:

await transporter.sendMail({
from: "your-script-account@gmail.com",
to: "recipient@example.com",
cc: "friend@example.com",
bcc: "otherfriend@example.com",
subject: "Subject line",
text: "Body",
});

SMS email gateways

Same concept in JavaScript — just send an email to number@gateway:

// Using nodemailer
await transporter.sendMail({
from: "your-script-account@gmail.com",
to: "2125551234@vtext.com", // Verizon SMS gateway
subject: "",
text: "Reminder: bring umbrella!",
});

Same disadvantages apply: unreliable delivery, no failure notification, possible blocking. For reliable SMS, use Twilio:

npm install twilio
const twilio = require("twilio");

const client = twilio("ACCOUNT_SID", "AUTH_TOKEN");

await client.messages.create({
body: "Hello from Node!",
from: "+15551234567", // your Twilio number
to: "+15559876543",
});

Push notifications with ntfy

ntfy works with any HTTP client — no special package needed.

Sending notifications

Using built-in fetch (Node 18+):

await fetch("https://ntfy.sh/AlSweigartZPgxBQ42", {
method: "POST",
body: "Hello, world!",
});

Metadata: title, priority, tags

await fetch("https://ntfy.sh/AlSweigartZPgxBQ42", {
method: "POST",
body: "The rent is too high!",
headers: {
Title: "Important: Read this!",
Tags: "warning,neutral_face",
Priority: "5",
},
});

Receiving notifications in JavaScript

Poll with fetch and parse newline-separated JSON:

async function pollNotifications(topic, since) {
let url = `https://ntfy.sh/${topic}/json?poll=1`;
if (since) url += `&since=${since}`;

const resp = await fetch(url);
const text = await resp.text();

const notifications = text
.trim()
.split("\n")
.filter((line) => line)
.map((line) => JSON.parse(line));

return notifications;
}

// Usage
const msgs = await pollNotifications("AlSweigartZPgxBQ42", "10m");
for (const msg of msgs) {
if (msg.event === "message") {
console.log(msg.message); // 'Hello, world!'
console.log(msg.title || ""); // optional title
console.log(new Date(msg.time * 1000)); // convert epoch to Date
}
}

Important fields (same as Python):

  • id – unique message ID.
  • time – Unix timestamp (seconds). Convert with new Date(time * 1000).
  • event'message', 'open', 'keepalive', or 'poll_request'.
  • message – text content.
  • title, priority, tags – optional metadata.

Real-time streaming with EventSource

For real-time notifications without polling, use Server-Sent Events:

const EventSource = require("eventsource"); // npm install eventsource

const es = new EventSource("https://ntfy.sh/AlSweigartZPgxBQ42/sse");

es.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
console.log("New notification:", data.message);
});

Overall idea of the chapter

Chapter 20 in JavaScript: Gmail access with googleapis (full API) or nodemailer (send-only), SMS gateways via email (same number@carrier trick) or Twilio for reliability, and ntfy push notifications with plain fetch (POST to send, GET with ?poll=1 to receive, or EventSource for real-time streaming). Same safety rules: dedicated script account, dry runs, treat tokens and topic names like passwords.