Sending Email Is Easy. Sending It Well Is Not.
Calling an email API is one line of code. Making it reliable in production takes more thought. Here are the patterns that matter.
1. Never Send Synchronously
Do not call the email API in your request handler. If the API is slow or down, your entire endpoint blocks.
// Bad: blocks the request
app.post('/signup', async (req, res) => {
await smtp.emails.send({ ... }); // If this takes 5s, user waits 5s
res.json({ success: true });
});
// Good: queue it
app.post('/signup', async (req, res) => {
await queue.add('send-welcome-email', { userId: user.id });
res.json({ success: true }); // Returns immediately
});
2. Handle Errors Gracefully
Email API calls can fail. Network issues, rate limits, invalid addresses. Always wrap in try/catch and have a fallback.
try {
await smtp.emails.send({
from: '[email protected]',
to: user.email,
subject: 'Welcome!',
html: template,
});
} catch (error) {
// Log it, but do not crash the app
console.error('Email send failed:', error);
// Queue for retry
await retryQueue.add('email-retry', { to: user.email, template: 'welcome' });
}
3. Use Templates, Not Inline HTML
Hardcoded HTML in your application code is a maintenance nightmare.
// Bad
await smtp.emails.send({
html: '<div style="font-family: Arial"><h1>Welcome ' + name + '</h1></div>',
});
// Good: use a template
await smtp.emails.send({
template_id: 'welcome',
variables: { name: user.name },
});
4. Validate Email Addresses Before Sending
Sending to invalid addresses hurts your sender reputation. Validate at the point of collection.
- Check format (basic regex is fine)
- Reject disposable email domains
- Consider email verification services for critical flows
5. Set Up Webhooks
Do not poll the API to check email status. Use webhooks to get notified in real-time.
// Your webhook endpoint
app.post('/webhooks/email', (req, res) => {
const event = req.body;
switch (event.type) {
case 'delivered':
updateEmailStatus(event.email_id, 'delivered');
break;
case 'bounced':
handleBounce(event.email_id, event.recipient);
break;
case 'complained':
unsubscribeUser(event.recipient);
break;
}
res.sendStatus(200);
});
6. Monitor Your Sending
Track these metrics:
- Delivery rate - Should be above 98%
- Bounce rate - Should be below 2%
- Complaint rate - Should be below 0.1%
- Open rate - Varies by email type (transactional should be 60%+)
If any of these drift, investigate immediately before it affects your sender reputation.
7. Use Separate Domains for Different Email Types
Do not send marketing and transactional emails from the same domain. If your marketing emails get flagged, your transactional emails suffer too.
transactional: [email protected]
marketing: [email protected]
Key Takeaways
- Queue emails, never send synchronously
- Handle errors and implement retry logic
- Use templates for maintainability
- Validate addresses before sending
- Set up webhooks for delivery tracking
- Monitor your metrics weekly
- Separate transactional and marketing domains
Ready to get started?
Start sending transactional email today. Free to start, no credit card required.
Get Started for FreeRelated Posts
One approval instead of five DNS records: Domain Connect on SMTPfast
If your domain is on Cloudflare, SMTPfast can publish its sending records, and separately the inbound MX, through Domain Connect. You review each set on Cloudflare's screen, approve, and the records are written for you. Here is how it works, what gets written, and what to do when your DNS lives elsewhere.
Logs API and webhook deliveries: answer 'did it send?' without opening the dashboard
Two additions for the debugging side of email: the event log behind the Logs page is now an API, and every webhook has a delivery log with attempts, responses, automatic retries and a retry button.