When to use this workflow
The lookup pattern
The Volunteer API has noGET /users?email={exact} endpoint — there’s only email_like, which does a case-insensitive substring match. To do an exact-email lookup, query with email_like and then verify the match explicitly:
JavaScript
Why email_like and not exact match?
There’s no ?email= (exact) parameter — only ?email_like= (substring). For most email lookups this is fine — a substring of the full email matches uniquely in practice — but the pattern that handles edge cases is to always do a secondary equality check.
Three real edge cases to be aware of:
Edge case 1: substring collision
bruce@wayne.example and bruce@wayne.example.com are different addresses but email_like=bruce@wayne.example matches both:
JavaScript
Edge case 2: case sensitivity
JavaScript
Edge case 3: leading/trailing whitespace
User-supplied emails often have stray whitespace from copy-paste. Normalize before the API call:JavaScript
" bruce@wayne.example " (with spaces) against bruce@wayne.example. The lookup might “succeed” with the unnormalized input but return surprising data.
When findUserByEmail returns multiple results
Email is expected to be unique per VOMO organization, but defensive code handles the case of multiple matches:
JavaScript
- Migration bugs from a prior system
- Data import errors
- The email-change problem where an old record was never reconciled
When the lookup spans multiple pages
Theemail_like substring filter may return more results than fit on one page. For an exact-email lookup, the pattern is to find the match on the first page or accept that more searching is needed:
JavaScript
The find-or-create pattern
Often the use case isn’t “does this user exist?” but “get me this user — create if needed”:JavaScript
Why use find-or-create vs blind upsert?
The blind upsert handles both cases in one request, which is operationally simpler. Use find-or-create when:
For most sync workflows from a system-of-record, blind upsert is the right choice. For partner integrations that defer to VOMO as the source of truth for user data, find-or-create is safer.
A defensive lookup helper
A reference implementation that handles the common gotchas:JavaScript
- Validates the input is a non-empty string containing an
@ - Normalizes (trim, lowercase) before the API call
- Iterates pages if needed
- Filters to exact match
- Allows opt-in to “return all matches” for defensive workflows
- Falls back to most-recent-updated on unexpected duplicates
Performance and caching
For partner integrations that look up users frequently, the lookup cost adds up:When to cache
Cache the lookup result in two places:JavaScript
When NOT to cache
Common workflow patterns
Pattern 1: route data to an existing user
When external data arrives keyed by email and you need to find the VOMO user to attach it to:JavaScript
Pattern 2: precondition for downstream work
JavaScript
Pattern 3: deduplication before push
For external systems that may attempt to push the same user twice:JavaScript
Things to watch for
A few subtle issues that surface in production:email_like matching is sensitive to special characters
Emails with + (e.g., bruce+volunteer@wayne.example) need URL encoding handled correctly. URLSearchParams handles this; manual string concatenation doesn’t:
JavaScript
Email field can be missing or null
Defensive code checks foruser.email before calling .toLowerCase():
JavaScript
?. optional chaining handles the rare case where a User record has no email (edge cases from data migration, etc.).
Don’t look up by email in tight loops
For workflows that process many records, batching beats per-record lookup:JavaScript
Where to go next
Create or Update a User
The upsert workflow that pairs naturally with lookup-then-create patterns.
List Users with Filters
The bulk-read workflow useful for in-memory joins.
Users
The reference page with the full field shape and endpoint details.
Sync Users to External System
The end-to-end recipe that uses lookup, upsert, and full sync together.