Slack users.lookupByEmail: Get a User ID From an Email, Tested
users.lookupByEmail turns an email address into a Slack user ID. It needs the users:read.email scope. We ran it with the right and wrong emails, upper case, spaces and a token without the scope, in curl and Python, and list every response.
On this page
To get a Slack user ID from an email address, call users.lookupByEmail with email= and a token that has the users:read.email scope. Slack returns the full user object, and user.id is the ID (U...) you use for mentions and DMs. If no member of the workspace has that address, the answer is users_not_found. We ran each case below on 1 October 2026, with a test app in our own workspace.
Call users.lookupByEmail
Add users:read.email to the bot scopes (our app also had users:read), reinstall, and call:
curl -s https://slack.com/api/users.lookupByEmail \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
-d email=you@example.com
The top-level fields of our response, with our own address:
{"id": "U0B7L4YK420", "team_id": "T0B7JBCDKC1", "name": "sieun", "deleted": false, "real_name": "sieun", "tz": "Asia/Seoul", "is_admin": true, "is_owner": true, "is_bot": false, "is_email_confirmed": true}
That object sits under user in the response, next to "ok": true. It also has a profile with email, display_name, status_text and the avatar URLs. A GET request with ?email= worked too.
What different emails returned
What we sent as email | Result |
|---|---|
| The member's address | ok: true, U0B7L4YK420 |
| The same address in upper case | ok: true, U0B7L4YK420 |
| The address with a space before and after | users_not_found |
The address with +test added before the @ | users_not_found |
| An address no member has | users_not_found |
not-an-email | users_not_found |
| An empty string | users_not_found |
No email field at all | invalid_arguments |
Case does not matter, spaces do. Trim the input before the call, because a spreadsheet cell with a trailing space looks like an unknown person. A plus alias is a different address to Slack, even if the mail server delivers it to the same inbox. A missing field gives a clearer error than an empty one:
{"ok": false, "error": "invalid_arguments", "response_metadata": {"messages": ["[ERROR] missing required field: email"]}}
Slack checks the email against the address on the member's account, the one shown under Contact information on their profile. A person who signed up with a personal address and uses a work address in your company directory is users_not_found.
Never appear "away" on Slack again
Cloud-based. No downloads. Works 24/7 even when your laptop is off.
The scope error, and which token sees emails
Our user token had users:read but not users:read.email. The lookup with it returned:
{"ok": false, "error": "missing_scope", "needed": "users:read.email", "provided": "identify,channels:history,search:read,users:read,chat:write,search:read.public"}
Add the scope from needed to the same token type you call with, then reinstall; the steps are in missing_scope. The scope also changes other methods. users.info for the same person, with the user token that had only users:read, returned a profile with no email field and no error. With the bot token that had users:read.email, the field was there. Code that reads profile["email"] gets a KeyError with the first token and works with the second.
Look up a user by email in Python
We ran this with slack_sdk:
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
def user_id_for(email):
try:
return client.users_lookupByEmail(email=email)["user"]["id"]
except SlackApiError as e:
if e.response["error"] == "users_not_found":
return None
raise
print(user_id_for(os.environ["OWNER_EMAIL"]))
print(user_id_for("nobody-w1test@example.com"))
U0B7L4YK420
None
users_not_found is a normal answer, so the function returns None for it and raises for everything else, such as missing_scope or invalid_auth. With the ID you can mention the person as <@U0B7L4YK420> or open a DM; the other ways to find an ID, including by hand from a profile, are in Slack member ID. To post the result to a channel, see send a Slack message from Python.
FAQ
Can I look up many emails at once?
There is no batch method. One call takes one address. For a large list, users.list with users:read.email returns every member with their email, 200 per page, and you can match locally.
Stop Jiggling Your Mouse.
Join hundreds of remote workers who never worry about their Slack status. Set it up once, stay green forever.
Related Articles
Slack OAuth Redirect URL on localhost: What Slack Accepts, Tested
We added localhost, 127.0.0.1, https, custom-scheme and tunnel redirect URLs to a Slack app on 1 October 2026, then ran the OAuth flow against a local server. What was accepted, how Slack matches the URL, and the PKCE rules.
Slack App Manifest Example: YAML and JSON That Worked, Tested
A Slack app manifest we used to create a working app on 1 October 2026, in YAML and JSON, plus the validation errors from broken versions and real output from apps.manifest.validate, export, create and update.
Slack chat.delete API: Who Can Delete What, Tested
chat.delete removes a message by channel and ts. We deleted bot messages, a person's messages, a thread parent and an already deleted message with bot and user tokens, and list every response, including cant_delete_message and the tombstone a thread parent leaves.