Skip to content

Instantly share code, notes, and snippets.

@daniel-sc
Last active March 31, 2025 09:59
Show Gist options
  • Save daniel-sc/d5f258c7688a5a95404f8e21aa6f891e to your computer and use it in GitHub Desktop.
Save daniel-sc/d5f258c7688a5a95404f8e21aa6f891e to your computer and use it in GitHub Desktop.
This TypeScript script reads client_id and client_secret from CLI parameters, retrieves an OAuth access token from Zoho, and schedules emails to a list of recipients with a random delay. The email content and recipient list are read from local files.
import {readFileSync} from 'fs';
const args = process.argv.slice(2);
const clientIdIndex = args.indexOf('--client_id');
const clientSecretIndex = args.indexOf('--client_secret');
const subjectIndex = args.indexOf('--subject');
const fromAddressIndex = args.indexOf('--from_address');
const delayIndex = args.indexOf('--delay');
if (clientIdIndex === -1 || clientSecretIndex === -1 || subjectIndex === -1 || fromAddressIndex === -1) {
console.error('Please provide --client_id, --client_secret, --from_address and --subject arguments.');
process.exit(1);
}
const CLIENT_ID = args[clientIdIndex + 1];
const CLIENT_SECRET = args[clientSecretIndex + 1];
const SUBJECT = args[subjectIndex + 1];
const FROM_ADDRESS = args[fromAddressIndex + 1];
const DELAY = delayIndex !== -1 ? parseInt(args[delayIndex + 1], 10) : 60; // Default to 60 minutes if not provided
async function getAccessToken(): Promise<string> {
const res = await fetch('https://accounts.zoho.eu/oauth/v2/token', {
method: 'POST',
headers: {},
body: new URLSearchParams({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: 'client_credentials',
scope: 'ZohoMail.accounts.READ,ZohoMail.messages.CREATE'
})
});
if (!res.ok) {
throw new Error(`Failed to get access token: ${await res.text()}`);
}
const {access_token} = await res.json();
return access_token;
}
const ZOHO_ACCESS_TOKEN = await getAccessToken();
async function getAccountId(): Promise<string> {
const res = await fetch('https://mail.zoho.eu/api/accounts', {
headers: {
Authorization: `Zoho-oauthtoken ${ZOHO_ACCESS_TOKEN}`
}
});
return (await res.json()).data[0].accountId;
}
const ACCOUNT_ID = await getAccountId();
const ZOHO_API_URL = `https://mail.zoho.eu/api/accounts/${ACCOUNT_ID}/messages`;
const emailContent = readFileSync('./zoho-email.html', 'utf-8');
const recipients = readFileSync('./zoho-recipients.txt', 'utf-8')
.split('\n')
.map(e => e.trim())
.filter(Boolean);
const randomOffsetMinutes = () => Math.floor(Math.random() * DELAY * 0.1); // Random offset up to 10% of the delay
function pad2(number: number) {
return number < 10 ? `0${number}` : `${number}`;
}
const scheduleEmails = async () => {
let delayMinutes = DELAY;
for (const recipient of recipients) {
const sendTime = new Date(Date.now() + delayMinutes * 60_000);
const emailPayload = {
fromAddress: FROM_ADDRESS,
toAddress: recipient,
subject: SUBJECT,
content: emailContent,
isSchedule: true,
scheduleType: 6,
scheduleTime: `${pad2(sendTime.getMonth() + 1)}/${pad2(sendTime.getDate())}/${sendTime.getFullYear()} ${pad2(sendTime.getHours())}:${pad2(sendTime.getMinutes())}:${pad2(sendTime.getSeconds())}`,
timeZone: 'Europe/Berlin'
};
const res = await fetch(ZOHO_API_URL, {
method: 'POST',
headers: {
Authorization: `Zoho-oauthtoken ${ZOHO_ACCESS_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(emailPayload)
});
if (!res.ok) {
console.error(`Failed to schedule email to ${recipient}:`, await res.text());
} else {
console.log(`Scheduled email to ${recipient} at ${sendTime}`);
}
delayMinutes += DELAY + randomOffsetMinutes();
}
};
scheduleEmails().catch(console.error);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment