A user connected their Google account to your app last week. Tonight your backend needs to read their calendar, pull the next day’s meetings, and queue reminder email, all without that user being online. That’s the work that happens after OAuth: your server makes authenticated requests against their connected account on a schedule you control.
This recipe picks up where connecting accounts with OAuth leaves off. You already have a grant_id. Now you use it with your API key to read calendars, send mail, and act on each user’s data from your backend.
Use the grant ID to act on an account
Section titled “Use the grant ID to act on an account”A grant represents one connected account, and its grant_id is the address you target for every request on that user’s behalf. You authenticate with your API key as a Bearer token, then put the grant_id in the path. One integration covers all 6 providers, Google, Microsoft, Yahoo, iCloud, IMAP, and Exchange, with the same request shape.
You never send the user’s provider token. Nylas stores the access and refresh tokens on the grant and refreshes them before they expire, so a grant stays usable for months without any token code in your app. The request below lists events for one account: pass the API key in the header and the grant_id in the path, plus a required calendar_id to pick which calendar.
curl --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=primary' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'import Nylas from "nylas";
const nylas = new Nylas({ apiKey: process.env.NYLAS_API_KEY });
const events = await nylas.events.list({ identifier: grantId, queryParams: { calendarId: "primary" },});import osfrom nylas import Client
nylas = Client(api_key=os.environ.get("NYLAS_API_KEY"))
events = nylas.events.list( grant_id, query_params={"calendar_id": "primary"},)Swap events for messages, contacts, or notetakers and the model stays the same: same API key, same grant_id, different resource path.
Read a user’s calendar on their behalf
Section titled “Read a user’s calendar on their behalf”Read a connected user’s calendar with a GET /v3/grants/{grant_id}/events request. This answers the common need to access Google Calendar on behalf of a user after they sign in once. The endpoint requires a calendar_id, where primary targets the user’s default calendar, and it works the same across Google, Microsoft, and other connected providers.
Pass a start and end window as Unix timestamps to scope the read to a date range, which is what a nightly job wants when it pulls tomorrow’s meetings. The call below fetches events from one calendar inside a time window. Nylas returns up to 50 events per page by default, and you page through the rest with the page_token cursor from each response.
curl --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/events?calendar_id=primary&start=1735689600&end=1735776000' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'const events = await nylas.events.list({ identifier: grantId, queryParams: { calendarId: "primary", start: "1735689600", end: "1735776000", },});events = nylas.events.list( grant_id, query_params={ "calendar_id": "primary", "start": "1735689600", "end": "1735776000", },)Because the grant already holds calendar scope, you don’t re-prompt the user or refresh a token to run this read. It works whether the user is online or asleep.
Send email as the user
Section titled “Send email as the user”Send mail from a user’s own mailbox with POST /v3/grants/{grant_id}/messages/send. The message goes out through the connected account, so recipients see the user’s real address and the sent copy lands in their provider’s Sent folder. You don’t configure SMTP or store mailbox credentials, and the same request works across all 6 supported providers.
This is how a reminder, a confirmation, or a reply gets delivered as the user rather than from a generic system address. Give the subject, body, and a to array. The API key and grant_id decide which mailbox sends. For the full message model, attachments, and tracking options, see sending email.
curl --request POST \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/send' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json' \ --data '{ "subject": "Your meeting reminder", "body": "Your 9:00 AM sync is confirmed for tomorrow.", "to": [{ "name": "Leyah Miller", "email": "[email protected]" }] }'const sent = await nylas.messages.send({ identifier: grantId, requestBody: { subject: "Your meeting reminder", body: "Your 9:00 AM sync is confirmed for tomorrow.", },});sent = nylas.messages.send( grant_id, request_body={ "subject": "Your meeting reminder", "body": "Your 9:00 AM sync is confirmed for tomorrow.", },)Things to know about acting on behalf of users
Section titled “Things to know about acting on behalf of users”Acting on behalf of users means your backend holds one long-lived secret, your API key, and uses it against many grants. The model is reliable, but a few operational facts decide whether it stays secure and keeps working. Treat the API key like a production database password: one leaked key exposes every connected account at once.
Your API key is a backend-only secret. It authenticates as your whole application, so it must live in a secrets manager or environment variable and never reach a browser, mobile binary, or public repository. If it leaks, rotate it from the Dashboard. Client-side code should call your backend, and your backend calls Nylas, so the key stays on the server every time.
Tokens refresh automatically. Nylas stores each provider’s access and refresh tokens on the grant and renews them before they expire, so a nightly job keeps working for months with zero refresh code. You hold only the non-secret grant_id and your API key, which keeps the riskiest credentials out of your database.
Handle expired or revoked grants. A grant stops working when the user changes their password, revokes access at the provider, or the provider invalidates the token, and the API then returns a 401. Subscribe to the grant.expired webhook to catch it early and prompt re-authentication, which preserves the grant ID and sync state. See handling grant expiry.
Request least-privilege scopes. A grant can only do what its scopes allow, so a read-only calendar job should hold read scope, not send-and-modify. Google sorts scopes into 3 tiers, and the restricted tier needs a security assessment before launch. The scopes reference lists the exact strings per API.
Rate limits apply per grant. Providers cap requests per connected account, so a job that hammers one mailbox can hit a 429 while other grants stay fine. Spread reads across time and use webhooks instead of tight polling loops so your server reacts to changes rather than asking for them.
What’s next
Section titled “What’s next”- Connect user accounts with OAuth for the hosted flow that produces the grant ID
- Handle grant expiry to detect and recover revoked grants
- Granular scopes reference for the exact scope strings per API
- Authentication overview for the full grant model and every auth method