To migrate a push or email SDK across a large codebase with a coding agent, first build a complete inventory of every place the old provider touches: SDK setup, device tokens, tags and segments, send calls, webhooks, templates, config, secrets and tests. Then have the agent turn that inventory into a checklist, replace one layer at a time behind your own interface, and verify against the original list. The inventory is the hard part. Direct imports are easy to find; wrappers, queue consumers and code in other repositories are where migrations lose messages.
Key takeaways
- Most of the risk sits in indirect usage: wrapper services, event handlers and repositories the agent has not cloned.
- Ask the agent for an inventory file first and a code change second. Review the list before any edit.
- Put your own notification or mail interface in front of the provider, then swap the implementation behind it.
- Verify by diffing the old inventory against the new code and by sending real test messages per message type.
- Pick the new provider by your constraints (channels, webhooks, data residency, pricing model), not by feature lists alone.
CodeAlive is our product. Some tools mentioned below are made by companies we cross-mention with.
What does a push or email provider leave behind in your code?
It leaves far more than the import line. In a codebase that has used OneSignal or SendGrid for a few years, provider-specific code usually shows up in eight places:
If your inventory has nothing in one of these rows, treat that as a gap to check, not as good news.
How do you find every usage with a coding agent?
Start with a text search for the obvious names, then ask the agent to follow the call graph outward from each hit. Claude Code, Cursor and Codex can all run grep-style searches over the open repository, and that covers the first pass:
The second pass is where text search stops being enough. A call like notifications.sendOrderShipped(user) does not mention the provider. To find it you need callers of the wrapper, callers of those callers, and the event handlers that trigger them. A prompt that works well for this:
Two things make this harder on a large system. First, the agent's context window fills up quickly when it reads dozens of files to trace callers by hand. Second, the wrapper and its callers are often in different repositories: a shared notifications package, a mobile app, a backend, an infrastructure repo. An agent working in one checkout cannot see the others.
This is where a code context tool helps. We build one, CodeAlive, which indexes repositories and serves semantic search and a relationship graph (files, symbols, calls, dependencies) to agents over MCP. The agent can ask for callers of sendOrderShipped across all indexed repositories instead of reading files one by one. We measured how much that graph actually helps in GraphRAG for code, and how accurate its call edges are in our code graph comparison. The short version: the graph helped more on the larger repository, and on a medium one the gain was within noise. For setup options with Claude Code and Cursor, see context on large codebases.
When CodeAlive is not a fit: if the whole migration lives in one repository of moderate size, rg plus the agent's own file reading is usually enough, and adding an indexed service is extra setup for little gain. It also needs your repositories indexed before it can answer, so it does not help with code you cannot connect.
An illustrative inventory row (not output from a real run):
How do you plan the replacement without losing messages?
Plan it as three moves: put an interface in front of the old provider, implement the new provider behind the same interface, then switch traffic per message type.
- Introduce your own interface. For push, something like
PushSender.send(userId, messageType, data). For email,Mailer.send(templateKey, recipient, data). Route every inventory row through it while still calling the old provider. This change has no visible effect, which makes it safe to ship on its own. - Map provider concepts. Tags, segments, template IDs and webhook event names rarely map one to one. Write the mapping table before writing code. Ask the agent to fill it from the inventory and mark every row it is unsure about.
- Migrate data. Device tokens and suppression lists (bounces, unsubscribes) have to move. Losing the suppression list means emailing people who opted out.
- Switch per message type. Use a flag per
messageTypeortemplateKey, starting with internal or low-volume messages. - Remove the old SDK last, after webhooks on the new side have handled real events for a while.
Keep the agent on a short leash in step 1. A large diff that touches every caller at once is hard to review. One pull request per service or per category from the inventory is easier to check.
How do you verify that nothing was lost?
Rerun the original inventory search against the migrated code and compare. Every row should now point at your interface or be deleted on purpose.
Then check behaviour, which a search cannot prove:
- Send one real test message per message type to a test device and a test inbox.
- Fire each webhook event type from the new provider and confirm your handler processes it.
- Confirm the old provider's env vars and secrets are removed from every environment, not only from code.
- Confirm tests now mock your interface, not the old client.
How the CodeAlive MCP evolved covers the search tools agents use for this kind of rerun.
What should you look at when choosing the new provider?
Look at channels, delivery events and webhooks, data and template migration support, and the pricing model against your volume. Below are a few options in each category, in no particular order. Facts come from each vendor's own site; check current terms before deciding.
Push and messaging
Whichever you pick, check the migration items from the inventory against it: can you import device tokens or suppression lists, which webhook events exist, and how template variables are expressed.
FAQ
Can a coding agent do the whole migration by itself?
It can do most of the search and much of the mechanical rewrite, but someone has to review the inventory and the concept mapping. Agents miss usages they cannot see, such as code in repositories outside their checkout or config in infrastructure repos.
Is grep enough to find all usages?
Grep finds direct references to the provider. It does not find code that calls your wrapper without naming the provider, which is why the second pass follows callers through the call graph.
Should I swap push and email at the same time?
Usually not. They share patterns (tokens or addresses, templates, webhooks) but have separate data to migrate. Doing one at a time keeps each set of pull requests reviewable.
What is the most common thing to forget?
Suppression lists and unsubscribe state for email, and token cleanup jobs for push. Both live outside the send path, so an inventory built only from send calls misses them.
Do I need CodeAlive for this?
No. For a single repository, rg and the agent's own reading are often enough. An indexed code context tool such as CodeAlive helps more when the provider is used across several large repositories.