How to share a Python script that runs as each user
You wrote a script that cleans up your inbox: it finds the newsletters you never read, unsubscribes and archives the backlog. Your teammates want it too. The problem is that it's your inbox. To run it for them, you'd need their Gmail access, and the usual ways of getting that are bad: their password, an app password they generate, or a token they paste into a chat.
This guide covers the difference between scripts that run as a shared account and scripts that run as each user, and how to share the second kind safely.
Two kinds of scripts
| Runs as a shared account | Runs as each user | |
|---|---|---|
| Example | Refund an order in Stripe, export signups from the database | Clean up my inbox, summarize my calendar, file my receipts |
| Credentials | One company key | Each person's own account |
| In Callables | A secret you set once | A connection each person makes |
| If it's missing | The run starts without it, and builders see a warning | The run doesn't start, and the person is asked to connect |
Most internal tools are the first kind. But once you start automating personal work, email, calendars and documents, you need the second, and that's where scripts get hard to share.
Declare a connection
A connection says "this function uses the account of whoever runs it". Declare it once and pass it to the functions that need it:
import requests
from callables import Connection, Group
google = Connection("google", provider="google", scopes=["gmail.modify"])
gmail = Group("gmail", title="Gmail", connections=[google])
@gmail.deploy(title="Recent senders")
def recent_senders(days: int = 7) -> list[dict]:
"""Who emailed you the most lately."""
headers = {"Authorization": f"Bearer {google.token}"}
...google.token is an access token for the person running the function. When Alice runs it, it's Alice's. When Bob runs it, it's Bob's. Your code is the same either way, and nobody's credentials ever appear in it.
You bring the OAuth app, so Google's sign-in screen shows your app's name and exactly what it asks for. See creating a Google OAuth app. Callables handles everything around it: the sign-in, storing and refreshing each person's tokens, and handing the right token to each run.
What your teammates see
The first time someone opens the function, they see a Connect Google button where Run would be. It opens Google's own sign-in page. They choose their account and allow access, they're sent back, and they can run it.
From then on it just works:
- Other functions in the project that use the same connection run right away.
- If you add a scope, their next run asks them to reconnect and grant it.
- If they untick a permission on Google's screen, the connection is refused with a message naming what's missing, instead of failing halfway through a run.
- The Connections page in the sidebar lists everything they've connected. Disconnect deletes their tokens from Callables and revokes them with Google.
Whose account a run uses
| How the run started | Account it uses |
|---|---|
| Someone runs it from the form | Theirs |
| A schedule | The person who made the schedule |
A run never borrows someone else's connection. If Bob schedules "archive newsletters every Monday", it runs against Bob's inbox only.
Who can see what
This is the part to get right when you share access to people's accounts:
- Builders never see tokens in the web app, and tokens are hidden in run logs, like secrets.
- Tokens are encrypted at rest, and only the API and the run workers decrypt them.
- Your code does receive the token during a run, because it has to call Gmail. Someone running your function is trusting you with their account, the same as installing any app. So ask for the narrowest scope that works:
gmail.readonlyif you only read. - Each run is its own process, so one person's token can't leak into another person's run.
Mixing both
Functions often need both kinds: their own inbox plus a company key. Declare both, and each comes from the right place:
@gmail.deploy(title="Log my customer emails to the CRM", secrets=["CRM_TOKEN"])
def log_customer_emails(days: int = 1) -> str:
"""Copies your recent emails with customers into the CRM."""
... # google.token is yours; os.environ["CRM_TOKEN"] is the company'sTry it
The Gmail examples in the cookbook are a complete project: inbox statistics, recent senders, archive a sender, and finding mailing lists with one-click unsubscribe. Test it on your own machine with call connect, then push and share it with your team.
Turn your scripts into tools your team can run
Add @deploy to a Python function and push. Your team gets a form, schedules and run history.
Keep reading
How to test OAuth connections locally
Sign in to Gmail, Slack or any OAuth service from your terminal with call connect, then run your functions against your own account before you push.
GuidesHow to share Python scripts with non-technical teammates
Your script works, and now someone in ops needs to run it without a terminal. The usual options, what each one costs, and how to get a script ready for other people.
GuidesDeploy from a monorepo or subfolder, with system packages
Build just one folder of a bigger repo, choose the Python version, install dependencies and add tools like ffmpeg with apt.txt.