Last active
March 31, 2025 09:59
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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