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.