Skip to main content
CodeAlive 3.0: From Context Engine to Code Research Agent
CodeAlive

Back to blog

Migrating a push or email SDK across a large codebase with a coding agent

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:

AreaWhat to look forWhy it is easy to miss
SDK initialisationApp IDs, API client construction, mobile SDK setup in app startupOften split between backend, iOS, Android and web
Device tokens and user IDsToken registration, external user ID mapping, token cleanup jobsStored in your database under provider-specific column names
Tags and segmentsCalls that set user tags, segment names hardcoded in campaignsSegment names live in strings, not types
Send callsNotification and mail API calls, batch sends, scheduled sendsFrequently hidden behind a Notifier or Mailer class
Webhooks and eventsDelivery, open, bounce and unsubscribe handlersLive in a separate service or a serverless function
TemplatesTemplate IDs, dynamic data payloads, localisation keysTemplate IDs are opaque strings in config
Config and secretsEnvironment variables, Helm values, Terraform, CI secretsNot in application code at all
TestsMocks of the provider client, fixtures with provider payloadsTests pass against mocks after the real call is gone

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:

bash
rg -n -i "onesignal|sendgrid|@sendgrid/mail|SENDGRID_API_KEY|ONESIGNAL_APP_ID" \  --glob '!node_modules' --glob '!*.lock'

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:

text
Build an inventory of everything that depends on our push provider (OneSignal).1. Find direct usages: imports, client construction, env vars, config keys.2. For every function or class that wraps the provider, list all of its callers,   recursively, until you reach an HTTP handler, queue consumer, job or UI event.3. Find webhook handlers that parse provider payloads.4. List tests and fixtures that mock the provider.Write the result to migration-inventory.md as a table:file, symbol, category, direct/indirect, notes. Do not change any code yet.

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):

markdown
| file | symbol | category | direct/indirect | notes || --- | --- | --- | --- | --- || services/orders/src/shipping.ts | onShipmentCreated | send call | indirect | calls Notifier.orderShipped -> OneSignal |

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.

  1. 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.
  2. 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.
  3. Migrate data. Device tokens and suppression lists (bounces, unsubscribes) have to move. Losing the suppression list means emailing people who opted out.
  4. Switch per message type. Use a flag per messageType or templateKey, starting with internal or low-volume messages.
  5. 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.

text
Re-run the inventory from migration-inventory.md against the current code.For each original row, report: migrated, deleted (with reason), or stillreferencing the old provider. Then search for any new direct provider usagethat is not in the original list.

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

ProviderWhat the official site saysFits whenDoes not fit when
PushwooshMobile and web push, in-app, email, SMS and WhatsApp in one platform; the pricing page states there is no feature gating between plansYou want push plus other channels and segmentation in one productYou only need raw token-to-device delivery and want no extra platform
Firebase Cloud MessagingA cross-platform messaging solution from Google for sending messages to appsYou need a low-level transport and will build segmentation yourselfYou want campaigns, journeys or analytics without building them

Email

ProviderWhat the official site saysFits whenDoes not fit when
UniOneEmail API and SMTP for transactional and marketing email, with webhooks for delivery events; the pricing page lists a four-month free trial with 6,000 emails a monthYou send both transactional and marketing mail through one APIYou need email inside an existing cloud account's billing and IAM
Amazon SESAn email service for sending from within any applicationYou already run on AWS and want email under the same accountYou want hosted templates, an editor and marketing tooling out of the box
PostmarkAn email delivery service for developers, for transactional and marketing emailYou want a focused developer-oriented senderYou also need push or SMS from the same vendor

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.

Coding AgentsContext Engineering

Give your agents the whole codebase

Index your first repo in minutes — or try the Playground without signing up.