Transactional Email API Guide: Verification, Password Reset, and Notifications
Transactional emails are personalized emails triggered by user actions, such as registration verification, password reset, and order confirmations. Unlike marketing emails, transactional emails are "expected" by users, resulting in 70-90% open rates.
1. Transactional vs Marketing Email
| Feature | Transactional | Marketing |
|---|---|---|
| Trigger | User action | Scheduled batch |
| Personalization | High | Medium |
| Open rate | 70-90% | 20-30% |
| Frequency | On demand | Scheduled |
| Unsubscribe | Should not include | Must include |
| Delivery requirement | Must deliver | Best effort |
A high open rate is a double-edged sword: precisely because users expect these emails, a failure to deliver, a trip to the spam folder, or a broken link costs far more than a single message — it costs trust in your product. A user who never receives a password reset may churn to a competitor; a late order confirmation means an extra support ticket. Treat transactional email as a critical path, not a marketing channel.
2. Common Email Types
2.1 Registration Verification
const crypto = require('crypto');
function generateVerificationToken(userId) {
const token = crypto.randomBytes(32).toString('hex');
const expiresAt = Date.now() + 24 * 60 * 60 * 1000;
saveToken({ userId, token, expiresAt, type: 'email_verification' });
return token;
}
2.2 Password Reset
async function requestPasswordReset(email) {
const user = await findUserByEmail(email);
if (!user) return { success: true }; // Don't reveal user existence
const token = crypto.randomBytes(32).toString('hex');
const resetUrl = `https://yourdomain.com/reset-password?token=${token}`;
await emailService.send({
to: email,
template: 'password-reset',
data: { username: user.name, resetUrl },
});
}
2.3 Order Notifications
async function sendOrderConfirmation(order) {
await emailService.send({
to: order.customerEmail,
template: 'order-confirmation',
data: {
orderNumber: order.number,
customerName: order.customerName,
items: order.items.map(item => ({
name: item.name,
quantity: item.quantity,
price: formatPrice(item.price),
})),
total: formatPrice(order.total),
estimatedDelivery: order.estimatedDelivery,
trackingUrl: order.trackingUrl,
},
});
}
2.4 Comparing Main Providers
| Provider | Free tier | Highlights | Best for |
|---|---|---|---|
| SendGrid | 100/month | Mature ecosystem, rich templates and analytics | Mid-to-large scale, detailed reporting |
| Mailgun | 100/month | Flexible API, developer-friendly | Deeply customized sending logic |
| Resend | 3,000/month | Native React Email support, quick to start | Small teams, fast validation |
| AWS SES | Pay per use (~$0.10/thousand) | Lowest price, AWS-native | High volume, cost-sensitive |
Beyond price, check three things: whether delivery and bounce events are reported via webhooks, whether a dedicated IP is available, and the limits of the free tier (many free quotas cap daily sends). Abstracting the provider behind a single EmailService interface (below) lets you switch providers without rewriting business code.
3. Template Management
- One template per scenario
- Parameterized with placeholders
- Multi-language support
- Responsive design
- Plain text version alongside HTML
Creating a separate template per scenario is not extra code for its own sake; it lets each email's structure and copy evolve independently. Order confirmations need a product list and tracking links, password resets need clear security guidance, and verification emails must state the expiry — forcing all of these into one template only gets messier. Version your templates after launch too: when a copy change drags down open rates, you can roll back and compare quickly. Don't skip the plain-text version — some clients and assistive tools render text only, and omitting it means giving up on part of your audience.
4. System Design
4.1 Queue Architecture
App Service → Message Queue (Redis/RabbitMQ) → Email Worker → Email API → Logging & Monitoring
Benefits: Async processing, retry mechanism, rate limiting, horizontal scaling
A realistic scenario: a sale day that sends 100,000 order confirmations. If every email is sent synchronously inside the order request, the checkout endpoint slows to hundreds of milliseconds and both the database and the mail provider get hammered. The right approach is to push send tasks into a queue (Redis or RabbitMQ) and let standalone workers consume them asynchronously: the API responds instantly, workers respect the provider's rate limits, and failures re-enter a retry queue. This addresses three problems at once — user-facing response time, provider rate limiting, and retries. Even if a batch of emails is delayed by half an hour, users generally find that acceptable.
5. Security
5.1 Rate Limiting
function checkRateLimit(email, type) {
const key = `${email}:${type}`;
const windowMs = 60 * 1000;
const maxAttempts = 3;
// Check and enforce limits
}
5.2 Token Security
- Use crypto.randomBytes for token generation
- Set reasonable expiration
- Invalidate tokens after use
- Track failed attempts
5.3 Deliverability: SPF / DKIM / DMARC
However important a transactional email is, it is worthless if it never reaches the inbox. Deliverability rests on three records: SPF declares which servers are allowed to send mail for your domain, DKIM signs the message digitally, and DMARC tells recipients what to do with forged mail. Most providers hand you the exact DNS records during onboarding — just paste them into your domain registrar. Before launch, verify the configuration with Google Postmaster Tools or Mail Tester; details are in the email deliverability guide.
6. Monitoring
| Metric | Alert Threshold | Description |
|---|---|---|
| Send failure rate | > 3% | Rejected or timed out |
| Latency | > 5 minutes | Send to delivery delay |
| Bounce rate | > 2% | Invalid addresses |
| Queue backlog | > 1000 | Queue accumulation |
Alongside hard metrics like send failure rate and latency, watch the health of the content itself: a sudden drop in open rate may mean a broken template or a trip to spam; abnormal click rates can indicate links blocked by email clients; a persistently rising bounce rate points to weak address validation at signup. Feed webhook events (delivered, opened, clicked, bounced, complained) into your logs so every email has a complete lifecycle record.
6.3 Pre-Launch Checklist
- A dedicated, parameterized template for each sending scenario
- Tokens generated with
crypto.randomBytes, expiring within 24 hours, invalidated after use - Password reset does not reveal whether an account exists
- Sending runs through a queue + workers with automatic retries
- SPF/DKIM/DMARC configured and verified
- Webhook events persisted and key metrics alerted
7. Summary
Transactional emails are critical for user communication. Use mature third-party services, implement queues and retry mechanisms, and don't forget monitoring.
Reference: RFC 5321 (SMTP protocol) https://datatracker.ietf.org/doc/html/rfc5321
Reference: Google Postmaster Tools https://postmaster.google.com
Reference: React Email documentation https://react.email/docs/introduction