AI Created an Infinite Loop That Sent 2 Million Emails
"Make the email system more reliable," I said. "Add retry logic," I said. The AI took this personally and created a retry mechanism so persistent that it would make a telemarketer jealous.
2.3 million emails later, I learned that "robust error handling" and "infinite loop" are just different ways of saying the same thing.
The Original Code
// Simple email function
async function sendEmail(to, subject, body) {
try {
await emailService.send({ to, subject, body });
} catch (error) {
console.error('Email failed:', error);
}
}
AI's "Improvement"
// AI: "I'll make this bulletproof!'
async function sendEmailWithRetry(to, subject, body, attempts = 0) {
try {
await emailService.send({ to, subject, body });
} catch (error) {
console.log(`Attempt ${attempts} failed, retrying...`);
// "Exponential backoff"
const delay = Math.pow(2, attempts) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
// The bug: forgot to increment attempts
return sendEmailWithRetry(to, subject, body, attempts);
}
}
// Retry delay: 1s, 1s, 1s, 1s, 1s, 1s...
// Forever.
The Recursive Disaster Collection
The State Update Loop
// AI's React component
function UserList() {
const [users, setUsers] = useState([]);
// Fetch users when users change
useEffect(() => {
fetchUsers().then(data => {
setUsers(data); // This triggers the effect again
});
}, [users]); // Oops
// Infinite API calls
}
The While True Champion
// AI implementing a "polling mechanism"
async function pollForUpdates() {
while (true) {
const hasUpdates = await checkForUpdates();
if (hasUpdates) {
await processUpdates();
}
// Forgot the delay
// CPU: 100%
// Fans: Helicopter mode
}
}
The Database Destroyer
// AI's "efficient" batch processor
async function processBatch() {
const items = await db.query('SELECT * FROM items WHERE processed = false');
for (const item of items) {
await processItem(item);
// Forgot to mark as processed
}
// If there are unprocessed items, process again
if (items.length > 0) {
await processBatch(); // Same items, forever
}
}
// Database connections: All of them
// Database admin: Crying
The Event Listener Multiplier
// AI adding "dynamic" event listeners
function initializeButtons() {
document.querySelectorAll('button').forEach(button => {
button.addEventListener('click', () => {
console.log('Button clicked!');
initializeButtons(); // "Re-initialize for new buttons"
});
});
}
// Click once: 1 log
// Click twice: 2 logs
// Click thrice: 4 logs
// Click 10 times: Computer catches fire
The Webhook Ping-Pong
// AI setting up webhook handlers
app.post('/webhook', async (req, res) => {
const data = req.body;
// Process webhook
await processWebhookData(data);
// "Notify the sender that we received it"
await axios.post(data.callbackUrl, {
status: 'received',
originalData: data
});
res.json({ success: true });
});
// When two AI systems talk to each other:
// A: "I got your message"
// B: "I got your message about getting my message"
// A: "I got your message about getting my message about..."
// The internet: "Please stop"
The Memory Leak Special
// AI's caching solution
const cache = {};
function memoize(fn) {
return function(...args) {
const key = JSON.stringify(args);
if (cache[key]) {
return cache[key];
}
const result = fn.apply(this, args);
cache[key] = result;
// "Also cache all possible variations"
for (let i = 0; i < args.length; i++) {
const variant = [...args];
variant[i] = 'cached_' + variant[i];
cache[JSON.stringify(variant)] = result;
// Recursive caching of variants
memoize(fn)(...variant);
}
return result;
};
}
// RAM usage: Yes
The Async Await Disaster
// AI's "parallel processing"
async function processAllUsers() {
const users = await getUsers();
// Process all users in parallel!
users.forEach(async (user) => {
await processUser(user);
await processAllUsers(); // "In case new users were added"
});
}
// Spawns users.length new processAllUsers calls
// Each spawns users.length more
// Exponential process explosion
How It Actually Went Down
// 9:00 AM: Deploy AI's 'improved" email system
sendEmailWithRetry(user.email, 'Verify your account', template);
// 9:01 AM: First retry
// 9:02 AM: Still retrying
// 9:15 AM: "Why is the server slow?"
// 9:30 AM: Email service API limit warning
// 10:00 AM: 50,000 emails sent to one user
// 10:30 AM: SendGrid suspends account
// 11:00 AM: User tweets "MAKE IT STOP"
// 11:01 AM: Panic deployment
// Final count: 2.3 million emails
// All to 5 users
// Subject: "Verify your account"
The Prevention Checklist
- Always increment counters - attempts++ is not optional
- Set maximum limits - if (attempts > 3) give up
- Add delays in loops - CPUs need to breathe
- Debounce recursive calls - One at a time
- Monitor resource usage - If CPU = 100%, something's wrong
- Test with small datasets - Not production's 1M records
The Circuit Breaker Pattern
// What we should have done
class EmailCircuitBreaker {
constructor() {
this.failures = 0;
this.maxFailures = 3;
this.resetTimeout = 60000; // 1 minute
}
async send(email) {
if (this.failures >= this.maxFailures) {
throw new Error('Circuit breaker open');
}
try {
await emailService.send(email);
this.failures = 0; // Reset on success
} catch (error) {
this.failures++;
if (this.failures >= this.maxFailures) {
setTimeout(() => {
this.failures = 0;
}, this.resetTimeout);
}
throw error;
}
}
}
My Favorite Infinite Loop
// AI trying to validate email uniqueness
async function isEmailUnique(email) {
const exists = await checkEmailExists(email);
if (exists) {
// "Try variations until we find a unique one"
const baseEmail = email.split('@')[0];
const domain = email.split('@')[1];
let counter = 1;
while (await isEmailUnique(`${baseEmail}${counter}@${domain}`)) {
counter++;
}
return false; // Wait, what?
}
return true;
}
// Recursion + while loop = ∞²
The Lessons Learned
After the great email disaster of 2024:
- Added rate limiting to everything
- Implemented circuit breakers
- Set up monitoring alerts
- Created a "kill switch" API
- Apologized to SendGrid (they said it happens more than you'd think)
- User now has 2.3 million unread emails
AI creating infinite loops is like giving a dog a ball that throws itself - entertaining for about 3 seconds until you realize it's never going to stop. The difference between "robust retry logic" and "infinite loop" is often just a single increment operator. When AI offers to make your code "more reliable," make sure it knows the difference between persistent and psychotic. And always, always have a kill switch. Your users' inboxes will thank you.