Learn how to build an email automation using Replit. Follow our step-by-step guide to streamline your email tasks efficiently and easily.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
To build an email automation in Replit, you can use Node.js with a simple scheduler (using node-cron) and an email library like nodemailer. You’ll put the main logic inside an index.js file, store credentials safely in Replit Secrets, and test emails right from your Repl. Replit can stay awake if you upgrade to “Always On,” or you can manually run it when needed. This setup will let you send automated emails at chosen times or in response to an event in your app.
Create a new Node.js Repl (when asked “Language,” select Node.js). The main file (created automatically) is index.js. You’ll write your main logic there.
npm install nodemailer node-cron
Below is a working example of how to send an email. This goes inside your existing index.js file in the root directory.
const nodemailer = require("nodemailer");
const cron = require("node-cron");
// Create reusable transporter using SMTP
const transporter = nodemailer.createTransport({
service: "gmail", // You can also use another SMTP provider
auth: {
user: process.env.EMAIL_USER, // Loaded from your Replit Secrets
pass: process.env.EMAIL_PASS
}
});
// Function that sends an email
function sendMail() {
const mailOptions = {
from: process.env.EMAIL_USER,
to: "[email protected]", // Replace with a real address
subject: "Automated Email from Replit",
text: "This is an automated email sent from your Replit project!"
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.log("Error sending mail:", error);
} else {
console.log("Email sent successfully:", info.response);
}
});
}
// Run immediately to test
sendMail();
Run the Repl by clicking the “Run” button. Check the console for “Email sent successfully.” This proves that your SMTP credentials and Replit network connection work.
If you want to send emails automatically at certain times, add this code at the bottom of the same index.js file:
// Run every day at 9 AM UTC
cron.schedule("0 9 * * *", () => {
console.log("Running daily email automation...");
sendMail();
});
The “0 9 _ _ _” pattern is a cron expression — it means “run at 09:00 UTC every day.” You can customize that pattern as needed (for example, “_/5 _ _ _ _” runs every 5 minutes).
Important note: On a free Replit plan, your Repl sleeps after inactivity, so cron won’t run while it’s asleep. If you upgrade to a Hacker plan and turn on “Always On” in your project's settings, the schedule will keep working in the background.
When your project grows, you might want to clean things up to stay organized:
// Inside utils/mailer.js
const nodemailer = require("nodemailer");
let transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS
}
});
function sendMail(to, subject, text) {
const mailOptions = { from: process.env.EMAIL_USER, to, subject, text };
return transporter.sendMail(mailOptions);
}
module.exports = sendMail;
// Inside index.js
const cron = require("node-cron");
const sendMail = require("./utils/mailer");
cron.schedule("0 9 * * *", async () => {
await sendMail("[email protected]", "Daily report", "Your daily report is ready!");
console.log("Sent daily report email");
});
This setup gives you a functional email automation running completely inside Replit — using real code, handling credentials securely, and adaptable for production use if you connect it to a database or API later.
import express from "express";
import bodyParser from "body-parser";
import nodemailer from "nodemailer";
const app = express();
app.use(bodyParser.json());
// Simple in-memory queue to prevent sending duplicate emails too quickly
const emailQueue = new Map(); // key: recipientEmail, value: timestamp of last send
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.SENDER\_EMAIL,
pass: process.env.SENDER\_PASS
}
});
app.post("/send-email", async (req, res) => {
const { to, subject, message } = req.body;
if (!to || !subject || !message) return res.status(400).json({ error: "Missing fields" });
const lastSent = emailQueue.get(to);
if (lastSent && Date.now() - lastSent < 1000 \* 60) {
return res.status(429).json({ error: "Too many requests. Try again later." });
}
try {
await transporter.sendMail({
from: process.env.SENDER\_EMAIL,
to,
subject,
html: `${message}
`
});
emailQueue.set(to, Date.now());
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.listen(3000, () => console.log("Email service running on port 3000"));
import express from "express";
import axios from "axios";
import crypto from "crypto";
const app = express();
app.use(express.json());
// This endpoint triggers an automated email through a 3rd-party API (e.g., SendGrid)
// when a new customer is added via a webhook. Includes HMAC verification for security.
app.post("/webhooks/new-customer", async (req, res) => {
const signature = req.headers["x-signature"];
const secret = process.env.WEBHOOK\_SECRET;
const computed = crypto
.createHmac("sha256", secret)
.update(JSON.stringify(req.body))
.digest("hex");
if (computed !== signature) {
return res.status(401).json({ error: "Invalid signature" });
}
const { email, name } = req.body;
if (!email || !name) return res.status(400).json({ error: "Missing email or name" });
try {
await axios.post("https://api.sendgrid.com/v3/mail/send", {
personalizations: [{ to: [{ email }], subject: "Welcome!" }],
from: { email: process.env.SENDER\_EMAIL },
content: [
{
type: "text/html",
value: \`
Hello ${name}, welcome aboard!
We're excited to have you. Here's a quick start guide.
\`
}
]
}, {
headers: {
Authorization: `Bearer ${process.env.SENDGRID_API_KEY}`,
"Content-Type": "application/json"
}
});
res.json({ success: true });
} catch (err) {
console.error("Failed to send email:", err.message);
res.status(500).json({ error: "Failed to send email" });
}
});
app.listen(3000, () => console.log("Webhook listener running on port 3000"));
import express from "express";
import nodemailer from "nodemailer";
import Database from "@replit/database";
const db = new Database();
const app = express();
app.use(express.json());
// Automated email retry queue using Replit DB to persist state across runs
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.SENDER\_EMAIL,
pass: process.env.SENDER\_PASS,
},
});
async function enqueueEmail(job) {
const queue = (await db.get("email\_queue")) || [];
queue.push({ ...job, attempts: 0 });
await db.set("email\_queue", queue);
}
// Simple interval-based job runner (simulate cron)
setInterval(async () => {
const queue = (await db.get("email\_queue")) || [];
if (queue.length === 0) return;
const remaining = [];
for (const job of queue) {
try {
await transporter.sendMail({
from: process.env.SENDER\_EMAIL,
to: job.to,
subject: job.subject,
html: job.html,
});
console.log(`Email sent to ${job.to}`);
} catch (err) {
job.attempts += 1;
if (job.attempts < 3) remaining.push(job);
else console.error(`Failed after 3 attempts: ${job.to}`, err.message);
}
}
await db.set("email\_queue", remaining);
}, 1000 \* 60); // every minute
app.post("/schedule-email", async (req, res) => {
const { to, subject, html } = req.body;
if (!to || !subject || !html) return res.status(400).json({ error: "Missing fields" });
await enqueueEmail({ to, subject, html });
res.json({ success: true, message: "Email scheduled" });
});
app.listen(3000, () => console.log("Email automation service running on port 3000"));

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
If you want to build a reliable email automation system on Replit, use a Node.js backend (Express is a good choice), handle your API keys safely using Replit Secrets, use a trusted mail service like Nodemailer (for SMTP) or a transactional email API (like SendGrid), and separate your logic into small clear files: one for server logic (index.js), one for email logic (mailer.js), and a simple HTML form (for testing) in index.html. Keep secrets out of your code — only use Replit Secrets tab. Replit runs 24/7 only if you use Deployments or Always On, so plan accordingly if you need scheduled or automated sending.
npm install express nodemailer
This file runs your Express server and handles requests that trigger emails. Place this file at your Replit project root (Replit does it automatically when created).
// index.js
import express from "express";
import { sendEmail } from "./mailer.js"; // Import the mailer function
const app = express();
app.use(express.json()); // enables reading JSON from requests
// create an endpoint for triggering email automation
app.post("/send", async (req, res) => {
const { to, subject, body } = req.body;
try {
await sendEmail(to, subject, body); // call the mailer
res.json({ success: true, message: "Email sent successfully!" });
} catch (error) {
console.error(error);
res.status(500).json({ success: false, error: "Failed to send email." });
}
});
app.listen(3000, () => {
console.log("Server running on port 3000");
});
This file contains the actual sending function, separated so you can reuse or test it independently. Always read credentials from process.env since Replit injects your secrets automatically.
// mailer.js
import nodemailer from "nodemailer";
// create and export reusable function
export async function sendEmail(to, subject, body) {
// Configure SMTP transport
const transporter = nodemailer.createTransport({
service: "gmail", // works for Gmail or you can change to custom SMTP
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
const mailOptions = {
from: process.env.EMAIL_USER,
to,
subject,
html: `<p>${body}</p>`, // HTML content of email
};
// send email
await transporter.sendMail(mailOptions);
}
To make it easy for non‑developers to trigger emails, you can have a small test form. In Replit, create index.html in the root folder and link it via Express’s static middleware or open it manually.
<!-- index.html -->
<!DOCTYPE html>
<html>
<body style="font-family:sans-serif;">
<h3>Send Test Email</h3>
<form id="emailForm">
<input type="email" id="to" placeholder="Recipient" required/><br/><br/>
<input type="text" id="subject" placeholder="Subject" required/><br/><br/>
<textarea id="body" placeholder="Email body"></textarea><br/><br/>
<button type="submit">Send Email</button>
</form>
<script>
const form = document.getElementById("emailForm");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const data = {
to: document.getElementById("to").value,
subject: document.getElementById("subject").value,
body: document.getElementById("body").value
};
const res = await fetch("/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
const result = await res.json();
alert(result.message || result.error);
});
</script>
</body>
</html>
console.log() generously when debugging.
express-rate-limit) if you publicly expose the send endpoint, to prevent abuse.
This approach respects Replit’s constraints, keeps your credentials safe, and gives you a working, realistic base for email automation — not just a demo, but something you can extend for real projects.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.