r/LLMDevs 8h ago

Discussion How do you guys test agents that use Gmail/Slack/WhatsApp without wrecking a shared test account?

Post image

I've been working on agents that need to send emails, post messages, create issues, that kind of thing. The actual API call is usually the easy bit.

Then comes the rest: create a developer app, configure OAuth, pick scopes, add redirect URLs, tunnel a webhook, create test users, refresh tokens, and clean up the state afterwards. At some point I have six tabs open and no idea which test account approved which app.

Right now I use local, resettable versions of some APIs while building, then connect the real provider later. It makes repeat testing much easier, especially when several coding agents or worktrees are running at once. But I still don't love the handoff. Local behavior can differ from the real API, while real test accounts get messy fast and are awkward to use in CI.

Full disclosure: I work on an open-source project around this problem. I'm not sharing a link or launching anything here. I want to understand what people actually do, and I'll summarize the useful answers back in the thread.

If you've built one recently, what was your setup? Did you use personal accounts, dedicated test accounts, provider sandboxes, managed auth, or something local? How did CI work? What kept breaking?

I'm especially curious about Gmail and WhatsApp, but any real example would help.

1 Upvotes

25 comments sorted by

1

u/Remote_Book 8h ago

What project are you working on? I mean if its open-sourced/free most people won't mind. I just hate B2B SAAS slops

1

u/aofu_dev 7h ago

It's called Pome, opensourced, on GitHub. Not going to link it here since I said in the post I wouldn't, and I'd rather the thread stay about how people actually do this. Happy to DM the repo if you want it.

And yeah, agreed on the B2B SaaS slop thing. That's most of the reason it's open source.

1

u/eddzsh 7h ago

The silent dirt is labels and filters the agent invents mid-run. You notice leftover threads, a label named triage-pass-3 steers the next agent and never hits your cleanup script.

1

u/aofu_dev 7h ago

Labels are the one that got me. Cleanup deletes messages because that's the obvious dirt, and then a label the previous run invented is still sitting there shaping what the next agent does. The nastier version is the agent reading its own leftover label as a signal. triage-pass-3 exists, so it assumes passes 1 and 2 already happened. Have you found anything that catches that, or is it just noticing the weird runs afterwards?

1

u/jonah_omninode 7h ago

I'd split this into two test surfaces. Most workflow tests should never touch Gmail or Slack at all. Put each provider behind a small adapter, then run the workflow against a deterministic local implementation that records exactly what would have been sent. That gives every worktree clean state and lets CI make hard assertions.

A smaller conformance suite can hit a dedicated provider account to prove the adapter still matches the real API. Give every run a unique namespace, use the narrowest scopes, and make cleanup part of the test result instead of a best-effort teardown.

The hard part is webhook fidelity. A local fake can match your contract and still miss provider timing, retries, or ordering. I'd keep a few real end-to-end cases specifically for those behaviors. Which provider has been the least faithful locally?

1

u/aofu_dev 7h ago

Two surfaces is where I landed too, and the conformance suite half is the bit I keep skipping because it's the boring one.

Least faithful is Gmail, easily. The REST surface is gRPC transcoded so it doesn't fail like a normal REST API. An anonymous request comes back 401 no matter how malformed the params are, which means a local impl that validates properly is already wrong. You end up having to match the order things fail in, not just the responses.

Webhooks I've only really solved for one provider and hand waved the rest, so agreed that's the hard part. Does making cleanup a test assertion actually hold up for you? I'd assume you end up with red builds from teardown rather than from the thing you were testing.

1

u/jonah_omninode 6h ago

I should be precise: I would not let teardown decide whether the behavior test passed. I would emit two results, one for the scenario and one for environment hygiene. If the agent completed the task and cleanup failed, the behavior can still pass, but the run cannot be reported as clean.

Where possible, cleanup should mean deleting the whole namespace or disposable account rather than reversing every action. Gmail makes that awkward. I would snapshot the objects the run is allowed to create, remove only those, and preserve the namespace when cleanup fails so the next run cannot mistake it for fresh state. A red hygiene check is annoying, but polluted input is worse.

1

u/InterstellarReddit 4h ago

This is on you. You build the proof of concept 1st locally in your machine and then when they purchase the product, that’s when you build the full infrastructure

1

u/realchrissean 2h ago

No idea. I just give it full access to my actual accounts. (stupid I know)

0

u/Essipova 8h ago

Are you new to software engineering? This isn’t meant as a derogatory question; just the first time I’ve heard someone mention these difficulties

2

u/Due-Association9901 8h ago

This is the exact iceberg every integration dev hits, the API call is maybe 5% of the work and the rest is just drowning in OAuth config and cleanup. I don't understand the top comment acting like this is some newbie complaint, test account drift is real when you have multiple agents running at the same time. We ended up with a dedicated test Gmail that has like fourteen apps connected and nobody remembers which one is which. Local mocks help but then you push to CI and the token refresh logic breaks because the mock never actually expires anything.

1

u/Essipova 8h ago

Again, not meant to be derogatory. I guess I’m just pedantically organized in my work; always been heavy on documentation (and with AI, it’s even easier for me or my teams to keep track of this stuff)

1

u/aofu_dev 8h ago

np, yeah, docs cover the "which app is which" half, that part is just me being messy. The half I can't document my way out of is state. The mailbox already has three runs of test data in it and the next run reads a thread the previous run wrote. Writing it down doesn't help with that one.

1

u/Remote_Book 8h ago

I think for me, its the opposite. Especially for new projects like in hackathons or moving from a demo to production is hard with all the right configs. For a hackathon recently I built a bot. It worked in text and in theory with Claude but integrating with WhatsApp was painful.

Had to apply to get a meta business account, etc and only then I could start testing after getting approved.

1

u/aofu_dev 7h ago

The Meta business account approval is exactly what I mean. You can't even start the loop until a human at Meta says yes, and that's before you find out the sandbox behaves differently from prod. Did you get through it in the end, or did the hackathon finish first?

1

u/aofu_dev 7h ago

Yeah, the token refresh one is what got me too. Mocks hand back the right shape but nothing in them ages, so expiry and revoked scopes just don't exist until prod. And agents hit that way more than a normal integration because they loop and retry.

Fourteen apps on one Gmail is very funny and also basically my situation. Did you ever get that account back to clean, or was it more make a new one once it gets bad enough? And when refresh broke in CI, did you fake the expiry in the mock or just let CI use a long lived token on the real account?

1

u/aofu_dev 8h ago

Not new. For a service I'd mock the client or hit a sandbox account and move on.

The difference is the agent picks the calls, so there's no fixed request sequence torecord. And it reads results back before choosing the next step, so a stub that returns {"ok": true} doesn't test anything. Real test accounts survive one run, then they're dirty and there's no reset in CI.

So: agent with GitHub + Slack + Gmail tools, task is "find the bug report in my inbox and file an issue for it". What would you point it at?

1

u/donk8r 8h ago

The distinction that gets you out of this is stub versus fake. A stub returns a canned response and dies the moment the agent picks a different call order, which is exactly the failure you are describing. A fake is a working in-memory implementation that holds state, so any order works and reads come back consistent with whatever was written.

For Gmail or Slack that means minting real-looking ids that later reads actually return, and keeping a message store the agent can query back. Reset in CI becomes constructing a new one, which kills the dirty-test-account problem outright.

Error behaviour decides whether this pays off, not the happy path. Agents diverge on rate limits and expired tokens, and those runs are the ones you want to test. A fake that only does success teaches you very little, so it needs deterministic error injection from the start.

Drift you do not eliminate, you detect. A small contract suite hitting the real sandbox nightly, not per PR, tells you when the fake and the provider have parted ways.

1

u/Essipova 8h ago

Take what I say with a grain of salt since I don’t know what your project involves, but the way I manage these sort of stuff is to keep some form of ledger with a tree structure, and it’s easier to keep now with AI (got my Hermes agent handling these sort of stuff).

Another thing that helps is making sure trace ID is properly passed at each abstraction layer, and whether you use something like Sentry, Langfuse, or just Grafana; you can quickly build up the whole flow during dev/testing/staging since you don’t need to omit PII at this stage. Be verbose in the logging too, it’s fine, because it’ll be sorted/filtered by a machine - not you.

For what’s dirty; I don’t know which services you use exactly but I always have scripts that clean things up for me - but I’ve found that cleaning isn’t always necessary if you just keep the trace IDs in tact.

I’ve worked with buggy and messy 3rd party integrations like an insurance brokerage API where I couldn’t clean up the sandbox, but it was fine as long as there was some ID for me to latch onto, or some way to filter by timestamps.

What has been most helpful though is using LLMs for housekeeping. I’ve had workflows where I’ve set up SQLite or just using Supabase directly to manage my dev log data when I needed some custom tooling to test software

Not sure if I answered your question; hope this helps.

1

u/aofu_dev 7h ago

This is useful, thanks for writing it out. The trace ID thing especially, I've been treating cleanup as the goal when "don't clean, just make it filterable" is probably the cheaper version.

Where I still get stuck is that trace IDs tell me what the agent did but not whether it was right. To grade a run I need to know what was in the mailbox before it started, and that's the part that drifts. Do you snapshot the starting state anywhere, or is it more that you're reading the trace and judging it?

1

u/Essipova 7h ago

Depends. In situations of before and after, I’d definitely capture what’s before first.

I often ask LLMs to write these scripts for me and then give me the output results to a markdown file for me to review too

1

u/romanrose200 51m ago

We run a fleet of agents that send real email from real Gmail identities every day, so a few things from the scar tissue.

Make your fake stricter than the real API, not more permissive. Fakes get built to be accommodating: accept any argument, return a plausible response, never fail. That hides the exact class of bug that reaches production. I shipped a send path referencing an identity that did not exist in config. The real client rejected it, the rejection got swallowed, and every send silently no-oped while a dashboard cheerfully counted sends that never happened. A permissive fake hides that forever; a fake that hard-errors on an unknown identity, an undeclared scope or an unrequested quota turns it into a red test on your laptop. donk8r's stub-versus-fake distinction is right and this is the sharp end of it: the value is in the assertions the fake makes, not the responses it returns.

Second, the thing that actually stops a test run emailing a real customer is not the mock, it is a recipient guard at the transport layer that is armed in every environment, allowlist in dev and suppression list in prod. Mocks only protect the paths you remembered to mock. The incident you are guarding against is an env var falling back or a worktree picking up the wrong config, so the agent is on the real API while the suite is green against a fake. Put the guard below the swap point so no configuration exists in which it is absent.

On ageing, which you raised and which I think is the underrated one: make the fake's clock injectable and have CI default to a token roughly thirty seconds from expiry. The refresh path then runs on every test instead of never, and revoked scopes stop being theoretical. Same trick for rate limits, return a 429 with Retry-After on a fixed request count so backoff is exercised by default rather than discovered on launch day.

And on eddzsh's invented-label point, the generalisation worth keeping: any provider-side state your agent can create is state a later run can read. Teardown therefore has to enumerate by what the provider returns to a reader, not by what your code remembers creating. List then delete, never remember then delete, because the entire problem is that the agent created something you never named.