Creating great email content means nothing if it never reaches the inbox. Email deliverability is the practice of ensuring your emails actually arrive where intended. Poor deliverability means wasted effort, lost revenue, and damaged sender reputation. This guide covers everything you need to achieve and maintain excellent inbox placement rates.
Understanding Email Deliverability
Email deliverability encompasses three main metrics:
Delivery Rate: Percentage of emails accepted by receiving servers Open Rate: Percentage of delivered emails that are opened Inbox Placement Rate: Percentage of emails that land in the primary inbox (not spam folder)
The goal is not just getting emails delivered, but getting them delivered to the inbox where recipients will see them.
Sender Reputation
Your sender reputation is the most critical factor in deliverability. Every sending IP and domain has a reputation score that mailbox providers use to decide whether to accept your emails.
What Affects Reputation
- Complaint Rate: Users marking emails as spam
- Bounce Rate: Invalid email addresses
- Engagement Metrics: Open rates, click rates, reply rates
- Volume Consistency: Sudden changes in sending volume
- List Hygiene: Quality and permission status of recipients
Checking Your Reputation
// lib/reputation-checker.ts
interface ReputationScore {
score: number;
status: 'good' | 'medium' | 'poor';
recommendations: string[];
}
export async function checkReputation(): Promise<ReputationScore> {
// Use services like:
// - Google Postmaster Tools
// - Microsoft SNDS
// - Sender Score
// - Barracuda Reputation
const checks = await Promise.all([
checkGooglePostmaster(),
checkSenderScore(),
checkBarracuda(),
]);
const averageScore = checks.reduce((sum, c) => sum + c.score, 0) / checks.length;
let status: 'good' | 'medium' | 'poor';
if (averageScore >= 80) status = 'good';
else if (averageScore >= 50) status = 'medium';
else status = 'poor';
return {
score: averageScore,
status,
recommendations: generateRecommendations(checks),
};
}Email Authentication
Authentication proves you're authorized to send from your domain. Without proper authentication, your emails are likely to be blocked or marked as spam.
SPF (Sender Policy Framework)
SPF specifies which mail servers are authorized to send email for your domain.
``txt ; SPF Record Example yourdomain.com. IN TXT "v=spf1 include:sendgrid.net include:_spf.google.com ~all"
Breaking it down:
- v=spf1: SPF version 1
- include:sendgrid.net: Authorize SendGrid to send
- include:_spf.google.com: Authorize Google Workspace
- ~all: Soft fail for unauthorized senders (use -all for hard fail)
### DKIM (DomainKeys Identified Mail)
DKIM adds a cryptographic signature to your emails, proving they haven't been tampered with.
``txt
; DKIM Record Example
sendgrid._domainkey.yourdomain.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC..."
Breaking it down:
- v=DKIM1: DKIM version
- k=rsa: Key type
- p=...: Public keyDMARC (Domain-based Message Authentication)
DMARC tells receiving servers what to do with emails that fail SPF or DKIM checks.
``txt ; DMARC Record Example _dmarc.yourdomain.com. IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com; ruf=mailto:forensics@yourdomain.com; aspf=s; adkim=r; pct=100"
Breaking it down:
- v=DMARC1: DMARC version
- p=quarantine: Policy (none, quarantine, reject)
- rua: Aggregate reports sent here
- ruf: Forensic reports sent here
- aspf=s: SPF strict mode (r for relaxed)
- adkim=r: DKIM relaxed mode
- pct=100: Apply policy to 100% of emails
### Implementing Authentication
// lib/email-auth.ts export async function setupAuthentication(domain: string) { // 1. Generate SPF record const spfRecord = v=spf1 include:sendgrid.net ~all;
// 2. Generate DKIM keys (usually done by your ESP) const dkimRecord = await generateDKIMRecord(domain);
// 3. Generate DMARC record const dmarcRecord = v=DMARC1; p=none; rua=mailto:dmarc@${domain};
return { spf: { type: 'TXT', name: domain, value: spfRecord, }, dkim: { type: 'TXT', name: sendgrid._domainkey.${domain}, value: dkimRecord, }, dmarc: { type: 'TXT', name: _dmarc.${domain}, value: dmarcRecord, }, }; }
## List Hygiene
### Maintain a Clean Email List
// lib/list-hygiene.ts export async function cleanEmailList() { // 1. Remove duplicates await removeDuplicateEmails();
// 2. Remove invalid emails await removeInvalidEmails();
// 3. Remove role addresses await removeRoleAddresses();
// 4. Remove bounced emails await removeBouncedEmails();
// 5. Remove unengaged subscribers await removeUnengagedSubscribers(); }
async function removeInvalidEmails() { const users = await db.users.findMany();
for (const user of users) { const isValid = await validateEmail(user.email);
if (!isValid) { await db.users.delete({ where: { id: user.id }, }); } } }
async function validateEmail(email: string): Promise<boolean> { // Syntax check const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return false; }
// MX record check const domain = email.split('@')[1]; try { const mxRecords = await resolveMx(domain); return mxRecords.length > 0; } catch { return false; } }
async function removeRoleAddresses() { const rolePrefixes = [ 'admin@', 'info@', 'support@', 'sales@', 'marketing@', 'abuse@', 'postmaster@', 'webmaster@', ];
for (const prefix of rolePrefixes) { await db.users.deleteMany({ where: { email: { startsWith: prefix, }, }, }); } }
async function removeUnengagedSubscribers() { const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);
const unengagedUsers = await db.users.findMany({ where: { lastEmailOpen: { lt: ninetyDaysAgo, }, }, });
// Send re-engagement campaign first await sendReengagementCampaign(unengagedUsers.map(u => u.email));
// Remove after 30 days if still no engagement setTimeout(async () => { await db.users.deleteMany({ where: { id: { in: unengagedUsers.map(u => u.id), }, lastEmailOpen: { lt: ninetyDaysAgo, }, }, }); }, 30 * 24 * 60 * 60 * 1000); }
## Content Optimization
### Avoid Spam Triggers
// lib/spam-checker.ts interface SpamCheckResult { isSpam: boolean; score: number; issues: string[]; }
export async function checkForSpamTriggers(email: { subject: string; html: string; text: string; }): Promise<SpamCheckResult> { const issues: string[] = []; let score = 0;
// Check for spam trigger words const spamWords = [ 'free', 'winner', 'buy now', 'click here', 'urgent', 'act now', 'limited time', 'amazing', 'incredible', ];
const content = ${email.subject} ${email.html} ${email.text}.toLowerCase();
for (const word of spamWords) { if (content.includes(word)) { issues.push(Contains spam trigger word: "${word}"); score += 10; } }
// Check subject line if (email.subject.length > 50) { issues.push('Subject line too long (max 50 characters)'); score += 5; }
if (/[^a-zA-Z0-9\s]/.test(email.subject)) { issues.push('Subject line contains special characters'); score += 10; }
// Check HTML/text ratio const htmlLength = email.html.length; const textLength = email.text.length;
if (htmlLength > 0 && textLength === 0) { issues.push('Missing plain text version'); score += 20; }
if (htmlLength > 0 && textLength > 0) { const ratio = htmlLength / textLength; if (ratio > 5) { issues.push('HTML to text ratio too high (should be < 5:1)'); score += 10; } }
// Check for image-only content if (email.html && !email.html.includes('<')) { issues.push('Email appears to be image-only'); score += 30; }
return { isSpam: score >= 50, score, issues, }; }
### Optimize Email Structure
// lib/email-optimizer.ts export function optimizeEmailStructure(content: string): string { // 1. Add alt text to images let optimized = content.replace( /<img(?![^>]*alt=)/gi, '<img alt="Image"' );
// 2. Ensure proper heading structure optimized = optimized.replace( /<h1>/gi, '<h1 style="font-size: 24px; font-weight: bold; margin: 0 0 16px 0;">' );
// 3. Add responsive styles optimized = <div style="max-width: 600px; margin: 0 auto;">${optimized}</div>;
// 4. Ensure text-to-image ratio const textLength = (optimized.match(/<[a-z][\s\S]*>/gi) || []).length; const imageCount = (optimized.match(/<img/gi) || []).length;
if (imageCount > textLength / 10) { console.warn('Too many images compared to text content'); }
return optimized; }
## Engagement Monitoring
### Track Email Metrics
// lib/engagement-tracker.ts export async function trackEngagement() { const stats = await db.emailAnalytics.groupBy({ by: ['userId'], _count: { id: true, }, where: { event: { in: ['open', 'click'], }, createdAt: { gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // Last 30 days }, }, });
const unengagedUsers = stats.filter((s) => s._count.id < 2);
// Segment unengaged users await segmentUnengagedUsers(unengagedUsers.map((s) => s.userId)); }
async function segmentUnengagedUsers(userIds: string[]) { await db.userSegments.update({ where: { name: 'Unengaged' }, data: { users: { connect: userIds.map((id) => ({ id })), }, }, }); }
## Warm-Up New IPs
Gradually increase sending volume from new IPs:
// lib/ip-warmup.ts interface WarmupSchedule { day: number; emailsPerDay: number; }
const warmupSchedule: WarmupSchedule[] = [ { day: 1, emailsPerDay: 50 }, { day: 2, emailsPerDay: 100 }, { day: 3, emailsPerDay: 200 }, { day: 4, emailsPerDay: 400 }, { day: 5, emailsPerDay: 800 }, { day: 6, emailsPerDay: 1600 }, { day: 7, emailsPerDay: 3200 }, { day: 8, emailsPerDay: 6400 }, { day: 9, emailsPerDay: 12800 }, { day: 10, emailsPerDay: 25600 }, ];
export async function warmupNewIP(ipAddress: string) { for (const schedule of warmupSchedule) { console.log(Day ${schedule.day}: Sending ${schedule.emailsPerDay} emails from ${ipAddress});
// Send emails evenly throughout the day const emailsPerHour = schedule.emailsPerDay / 24; const intervalMs = (60 * 60 * 1000) / emailsPerHour;
// Schedule emails for (let i = 0; i < schedule.emailsPerDay; i++) { setTimeout(async () => { await sendEmailFromIP(ipAddress); }, i * intervalMs); }
// Wait for next day await new Promise((resolve) => setTimeout(resolve, 24 * 60 * 60 * 1000)); } }
## Feedback Loops
Set up feedback loops with major ISPs:
// lib/feedback-loop.ts export async function setupFeedbackLoops() { // Major ISPs that offer feedback loops: // - AOL: http://postmaster.aol.com/FeedbackLoop // - Comcast: https://www.comcastsupport.com/fbl // - Yahoo: https://senders.yahooinc.com/yahoo-feedback-loop // - Microsoft: https://sendersupport.olc.protection.outlook.com/snl/fbl.aspx
const feedbackLoopEmail = abuse@${process.env.DOMAIN};
return { feedbackLoopEmail, instructions: 'Register this email with each ISP's feedback loop program', }; }
export async function handleFeedbackLoopReport(complaint: any) { const { email, complaintType, timestamp } = complaint;
// Log complaint await db.emailComplaints.create({ data: { email, type: complaintType, timestamp: new Date(timestamp), }, });
// Unsubscribe user await db.unsubscribeUsers.create({ data: { email, reason: 'spam_complaint', }, });
// Stop sending to this user await db.users.update({ where: { email }, data: { emailStatus: 'complained' }, }); }
## Monitoring and Alerts
### Real-time Monitoring
// lib/deliverability-monitor.ts export async function monitorDeliverability() { const stats = await getDeliverabilityStats();
// Check for issues if (stats.deliveryRate < 95) { await sendAlert(Delivery rate dropped to ${stats.deliveryRate}%); }
if (stats.complaintRate > 0.1) { await sendAlert(Complaint rate is ${stats.complaintRate}%); }
if (stats.bounceRate > 2) { await sendAlert(Bounce rate is ${stats.bounceRate}%); } }
async function getDeliverabilityStats() { const totalSent = await db.emails.count(); const totalDelivered = await db.emails.count({ where: { status: 'delivered' }, }); const totalBounced = await db.emails.count({ where: { status: 'bounced' }, }); const totalComplained = await db.emails.count({ where: { status: 'complained' }, });
return { deliveryRate: (totalDelivered / totalSent) * 100, bounceRate: (totalBounced / totalSent) * 100, complaintRate: (totalComplained / totalSent) * 100, }; }
## Conclusion
Email deliverability is an ongoing process, not a one-time setup. Key takeaways:
- **Authenticate emails** with SPF, DKIM, and DMARC
- **Maintain list hygiene** by removing invalid and unengaged addresses
- **Monitor reputation** through ISP tools and third-party services
- **Warm up new IPs** gradually
- **Optimize content** to avoid spam triggers
- **Handle complaints** promptly
- **Track engagement** to identify issues early
Remember: Deliverability affects your entire email program. Invest in maintaining good practices from the start, and you'll see better engagement, higher conversion rates, and a stronger sender reputation over time.
The ISPs' job is to protect their users from spam. Your job is to prove you're a legitimate sender who provides value. Following these best practices will help you achieve excellent inbox placement rates.