Your SPF, DKIM, and DMARC are green. Your sender reputation looks fine. But your password resets are still landing in spam.
Modern spam filters don't just check authentication — they pattern-match against thousands of signals. This is a field guide to the technical red flags that make Gmail and Outlook suspicious, even when your authentication is perfect.
Why authentication isn't enough
SPF/DKIM/DMARC prove identity — that you're allowed to send as your domain. They don't prove trustworthiness.
Inbox providers use machine learning models trained on billions of emails. They know what legitimate transactional email looks like — and what phishing, scams, and low-quality bulk mail look like. When your email matches the wrong pattern, authentication won't save you.
1. Suspicious link patterns
Links are the #1 phishing vector. Filters inspect every linkin your email for common scam patterns.
What triggers filters:
- Link text mismatch: Display text says "example.com" but href points to "sketchy-domain.xyz"
- URL shorteners: bit.ly, tinyurl, t.co — anything that hides the real destination
- IP addresses in links: http://192.168.1.100/reset instead of a proper domain
- Misleading anchor text: "Click here", "Verify now", "Urgent action required" (classic phishing language)
- Too many domains: Links to 5+ different domains in one email (looks like spam aggregation)
Safe alternatives:
<!-- ❌ Triggers filters -->
<a href="https://bit.ly/abc123">Reset your password</a>
<a href="https://yourapp.com/reset">Click here to reset</a>
<!-- ✅ Safe pattern -->
<a href="https://yourapp.com/reset?token=...">
Reset your password at yourapp.com
</a>2. HTML-to-text ratio problems
Spammers hide keyword-stuffing and sketchy content in HTML comments, hidden divs, or white-on-white text. Filters look for unusual ratios between visible text and HTML markup.
What looks suspicious:
- Excessive markup: 10KB of HTML for 3 sentences of text
- Hidden content: display:none, visibility:hidden, or font-size:0 text blocks
- Image-only emails: No meaningful text, just one big image with a link
- Missing plain-text version: HTML-only emails with no text/plain MIME part
Safe structure:
import { render } from "@react-email/render";
import { Resend } from "resend";
import PasswordResetEmail from "./emails/password-reset";
const resend = new Resend(process.env.RESEND_API_KEY);
const html = await render(<PasswordResetEmail resetLink={link} />);
const text = await render(<PasswordResetEmail resetLink={link} />, {
plainText: true, // Generate clean plain text version
});
await resend.emails.send({
from: "security@yourapp.com",
to: user.email,
subject: "Password reset requested",
html,
text, // Always include both
});3. Attachment red flags
Attachments are another phishing vector. Most transactional emails don't need attachments — and when you send them, filters scrutinize them heavily.
High-risk attachment types:
- Executable files: .exe, .bat, .sh, .app, .dmg
- Scripts: .js, .vbs, .ps1, .scr
- Compressed archives: .zip, .rar, .7z (especially if they contain executables)
- Office macros: .docm, .xlsm (macro-enabled documents)
- Suspicious PDFs: PDFs with embedded JavaScript or forms that request personal info
Safer approach:
Instead of attaching files, host them securely and link to download pages:
// ❌ High-risk: PDF attachment
await resend.emails.send({
attachments: [{ filename: "invoice.pdf", content: pdfBuffer }],
});
// ✅ Lower-risk: Secure download link
const downloadUrl = `https://yourapp.com/invoices/${invoiceId}/download`;
<P>
Your invoice is ready. <a href={downloadUrl}>Download invoice #{invoiceId}</a>
</P>4. Sender identity inconsistency
Filters track sender patterns over time. Sudden changes in how you identify yourself look suspicious.
Inconsistencies to avoid:
- Changing From address frequently: notifications@yourapp.com one day, support@yourapp.com the next, noreply@yourapp.com after that
- Mismatched display names: "YourApp Security" in one email, "Customer Support" in another, "YourApp Team" in a third
- Domain hopping: Sending from different domains or subdomains without warm-up
- Reply-To mismatch: From is automated@domain.com but Reply-To is a completely different service
Consistent sender identity:
// ✅ Pick ONE sender config and stick to it
export const EMAIL_CONFIG = {
from: "notifications@yourapp.com",
fromName: "YourApp",
replyTo: "support@yourapp.com", // Optional, keep consistent
} as const;
// Use everywhere
await resend.emails.send({
from: `${EMAIL_CONFIG.fromName} <${EMAIL_CONFIG.from}>`,
replyTo: EMAIL_CONFIG.replyTo,
// ...
});5. Urgency manipulation language
Phishing emails rely on urgency and fear to bypass rational thinking. Filters are trained to detect manipulative language patterns.
High-risk phrases:
- "Urgent action required immediately"
- "Your account will be suspended"
- "Verify your identity now or lose access"
- "Unusual activity detected - click here"
- "You have won / You have been selected"
- "Act now or expire"
- ALL CAPS SUBJECT LINES
- Multiple exclamation marks!!!
Calm, specific alternatives:
// ❌ Triggers spam filters
subject: "URGENT: Your Account Will Be Deleted!!!"
subject: "Action Required Immediately - Verify Now"
// ✅ Calm, specific, helpful
subject: "Password reset requested for your account"
subject: "Verify your email to complete signup"
subject: "Invoice #12847 from YourApp (Feb 2026)"In email copy, prefer calm explanations over manufactured urgency:
// ❌ Manipulative tone
<P>
URGENT: Your password reset link expires in 1 hour!
Click now or lose access forever!
</P>
// ✅ Calm, informative
<P>
You requested a password reset. This link is valid for 1 hour
for security reasons. If you didn't request this, you can safely
ignore this email.
</P>6. Authentication alignment failures
Even with SPF and DKIM configured, misalignment between From,Return-Path, and DKIM domain can trigger filters.
What alignment means:
- SPF alignment: The domain in
Frommatches the domain inReturn-Path(or they share the same organizational domain) - DKIM alignment: The DKIM signature domain (
d=) matches theFromdomain
Common misalignment scenario:
# ❌ Misaligned - looks suspicious
From: notifications@yourapp.com
Return-Path: bounce-12345@sendgrid.net
DKIM-Signature: d=sendgrid.net
# ✅ Aligned - passes DMARC
From: notifications@yourapp.com
Return-Path: bounce-12345@mail.yourapp.com
DKIM-Signature: d=yourapp.comHow to fix alignment:
- Use a custom sending domain with your email provider (Resend, Postmark, SendGrid, etc.)
- Configure DKIM to sign with your domain, not the provider's
- Set up a
MAIL FROMsubdomain that matches your organizational domain - Check
mail-tester.comto verify alignment is correct
7. Sudden volume spikes without warm-up
Inbox providers track sending patterns. A domain that sends 10 emails per day suddenly sending 10,000 looks like a compromised account or spam operation.
What triggers volume filters:
- Going from 0 to thousands of emails per day overnight
- Irregular bursts (100 emails Monday, 5 Tuesday, 500 Wednesday)
- Launching a new product with a massive email blast from a cold domain
- Sending from a new IP address without gradually ramping volume
Safe volume ramp (warm-up):
- Week 1: 50-100 emails/day to engaged users
- Week 2: 200-500 emails/day
- Week 3: 1,000-2,000 emails/day
- Week 4+: Gradually scale to target volume
8. Missing or broken unsubscribe mechanisms
Even transactional emails benefit from clear opt-out mechanisms. Filters know that legitimate senders make it easy to unsubscribe — scammers don't.
What filters look for:
- List-Unsubscribe header: RFC 8058 one-click unsubscribe support
- Visible unsubscribe link: Clear, accessible footer link
- Working mechanism: Links that actually unsubscribe, not broken 404 pages
- Respect preferences quickly: Process opt-outs within 48 hours
Implementation:
await resend.emails.send({
from: "notifications@yourapp.com",
to: user.email,
subject: "Your weekly summary",
html,
headers: {
// One-click unsubscribe (Gmail prominently displays this)
"List-Unsubscribe": `<https://yourapp.com/unsubscribe?token=${token}>`,
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
},
});And in your email template footer:
<P style={{ fontSize: "12px", color: "#666" }}>
Don't want these emails? {" "}
<a href={`https://yourapp.com/unsubscribe?token=${token}`}>
Unsubscribe
</a>
</P>Testing and monitoring
After fixing red flags, validate that your emails are actually landing in the inbox:
Testing tools:
- mail-tester.com: Comprehensive spam score analysis (free, instant)
- Google Postmaster Tools: Domain reputation monitoring for Gmail
- Microsoft SNDS: Sender reputation data for Outlook
- Your email provider's dashboard: Bounce rate, complaint rate, delivery stats
Healthy metrics:
- Bounce rate: <5% (ideally <2%)
- Complaint rate: <0.1% (people marking as spam)
- Open rate (transactional): 40-70% (for emails users expect)
- Spam score (mail-tester): 8.0+ / 10
Quick audit checklist
Run through this checklist before launching email flows:
- Links: All use your primary domain, no URL shorteners, descriptive anchor text
- Content balance: Both HTML and plain-text versions, reasonable markup-to-text ratio
- Attachments: Avoid if possible; if required, use safe formats and host securely instead
- Sender identity: Consistent From address, display name, and Reply-To across all emails
- Language: Calm, specific tone; no urgency manipulation or phishing clichés
- Authentication: SPF, DKIM, DMARC aligned with your From domain
- Volume: Gradual ramp-up if launching new flows; consistent sending patterns
- Unsubscribe: List-Unsubscribe header + visible footer link, working opt-out flow
When good emails still land in spam
If you've fixed everything and emails still get filtered:
- User behavior matters: If recipients consistently mark your emails as spam or don't open them, filters learn to route future emails to spam
- IP/domain reputation is historical: If your domain or IP was previously abused, you're starting from negative reputation
- Shared IP reputation: If you're on a shared sending IP, other users' behavior can affect you (consider dedicated IP if sending high volume)
- Recipient settings: Some users have aggressive personal spam filters or corporate security policies
Focus on user engagement: send emails people want, at times they expect them, with content that's genuinely useful. Technical fixes get you 80% of the way — the last 20% is earned through user trust.