User Communication
Tell users about deprecations and breaking changes through the channels they actually read.
Why
A deprecation communicated only in a changelog is a deprecation nobody heard — most users never read changelogs. Effective communication uses multiple channels with escalating urgency: a programmatic signal (Deprecation header / console.warn) that's impossible to miss for active callers, a changelog entry for the diligent, a direct email to the top callers by API key for the laggards, and a blog post for the broad audience. The principle is that the burden of notice is on you, not the user — if users are surprised by a removal, you failed to communicate, regardless of how many changelogs you wrote. Multi-channel communication with direct outreach to high-usage callers is what actually drives migration.
When
At every deprecation and every phase transition of the sunset timeline.
How
1. Programmatic: Deprecation + Sunset + Link HTTP headers on every response from the deprecated endpoint 2. In-code: console.warn / logger.warn on every call (SDK), @deprecated JSDoc (library) 3. Changelog: a "Deprecated" section with the feature, replacement, sunset date, and migration link 4. Direct email: query top 100 callers by API key + usage; email each a personalized migration guide 5. Blog post: a public "Upcoming Breaking Changes" post for broad reach + SEO 6. Dashboard: a deprecation status page (public or authenticated) showing each feature's sunset date 7. Escalate at each phase: announce (email all) -> remind (email laggards) -> restrict (warn in 429)
Example
# Multi-channel deprecation communication
# 1. HTTP headers (impossible to miss for active callers)
res.set('Deprecation', 'true');
res.set('Sunset', 'Sat, 31 Dec 2024 GMT');
res.set('Link', '</v2/orders>; rel="successor-version"');
# 2. In-code warning (SDK)
console.warn('[ez4code] /v1/orders is deprecated and will be removed 2024-12-31. Migrate to /v2/orders — see https://docs.ez4code.com/migrate-v1-v2');
# 3. Changelog entry
## Deprecated (2024-06-01)
- `GET /v1/orders` — use `GET /v2/orders` instead. Removed 2024-12-31.
Migration guide: docs/migrate-v1-v2.md
# 4. Direct email to top callers (script)
const topCallers = await db.query(`
SELECT api_key, count(*) as calls
FROM api_logs WHERE path LIKE '/v1/orders%'
GROUP BY api_key ORDER BY calls DESC LIMIT 100`);
for (const c of topCallers) {
await sendEmail(c.api_key_owner, 'migrate-v1-v2', { sunset: '2024-12-31' });
}Common Problems
- ⚠Communicating only in the changelog — the users most likely to break are the ones least likely to read it.
- ⚠A generic "we're deprecating some APIs" email with no specifics — users can't tell if it affects them, so they ignore it.
- ⚠No direct outreach to high-usage callers — the top 10 callers by volume are the ones who break loudest, and they're the easiest to identify.
Best Practices
- ✓Use 4+ channels: HTTP headers, in-code warn, changelog, direct email to top callers — redundancy is the point.
- ✓Personalize direct emails with the caller's specific usage ("you called /v1/orders 12,000 times last week") — specifics drive action.
- ✓The burden of notice is on you — if a user is surprised by a removal, you failed to communicate, not them to read.