If you’ve built a Google Chat bot, you’ve likely hit this wall: the API sends you a membership event with a User ID… but omits the email field entirely.
For Google Workspace developers, this limitation in the Chat API is a frequent stumbling block. As detailed in the User resource documentation, while you can use an email address as an alias in requests (e.g., referencing users/[email protected]), the API insists on returning only the canonical resource name (e.g., users/123456789). This ID corresponds to the user’s profile in the People API or Admin SDK, but the email itself is stripped from the response, forcing developers to perform a secondary lookup.
Handling User ID Conversions
I’ve explored this lookup logic before in Beyond the Limits: Automating Google Chat Space Archives. In that solution, I used a helper function to convert the users/{id} format to people/{id} for use with the Advanced People Service.
/**
* Fetches user details from the People API.
* @private
* @param {string} userResourceName The "users/{user}" string from the Chat API.
* @return {{name: string, displayName: string, email: string|null}} An object with user details.
*/
function getUserDetails_(userResourceName) {
const defaultUserDetails = {
name: userResourceName,
displayName: userResourceName.replace('users/', 'Unknown User '),
email: null,
};
// Fail fast for app users or invalid formats
if (!userResourceName.startsWith('users/') || userResourceName === 'users/app') {
return defaultUserDetails;
}
try {
const peopleApiResourceName = userResourceName.replace(/^users\//, 'people/');
const person = People.People.get(peopleApiResourceName, {
personFields: 'names,emailAddresses',
});
const displayName = person.names?.[0]?.displayName ?? defaultUserDetails.displayName;
const email = person.emailAddresses?.[0]?.value ?? null;
return {
name: userResourceName,
displayName: displayName,
email: email,
};
} catch (e) {
console.warn(`Could not fetch details for ${userResourceName} from People API: ${e.message}`);
return defaultUserDetails;
}
}
However, bridging this gap for a Service Account leads to a security dilemma. The People API often returns empty fields because the Service Account lacks a contact list. You might find yourself reaching for Domain-Wide Delegation to impersonate an admin—effectively using a sledgehammer to crack a nut.
In a recent guide, Justin Poehnelt outlines a more secure strategy that avoids granting blanket domain access. By assigning a custom “Users > Read” Admin Role directly to a Service Account, developers can resolve emails securely without the risks associated with full impersonation.
The Strategy at a Glance
- Custom Roles: Create a strictly read-only role in the Admin Console.
- Direct Assignment: Assign this role specifically to the Service Account’s email.
- No Long-Lived Keys: Use Application Default Credentials (ADC) in production and Service Account Impersonation for local development.
This approach ensures your bot has just enough permission to identify users, keeping your security team happy and your audit logs clean. For more information into the implementation, including the specific configuration steps, I encourage you to read Justin’s full post linked below.
Source: Resolve Chat User IDs to Emails: Least Privilege

Member of Google Developers Experts Program for Google Workspace (Google Apps Script) and interested in supporting Google Workspace Devs.

