Sending authenticated mail over SMTP is where most integrations stall. Google and Microsoft both turned off basic password auth, so SMTP now wants XOAUTH2: you mint an OAuth token, base64-encode it into the SMTP AUTH string, then refresh it on every expiry. App passwords are the usual fallback, but they’re turned off under enforced multi-factor authentication. On top of that, every provider has its own SMTP host, port, and transport security rules.
You don’t need any of it. Once a user connects their account, you send through their mailbox with a single HTTPS request, and the provider handles transport.
Send email through a connected mailbox
Section titled “Send email through a connected mailbox”Send a POST /v3/grants/{grant_id}/messages/send request to deliver mail through a user’s own mailbox over the provider’s API instead of SMTP. The body takes to (an array of {name, email} objects), subject, and body (HTML). The grant_id identifies the connected account, so the same request works across all 6 providers.
The request below sends one HTML message through the connected account. The endpoint is synchronous: it blocks until Gmail or Microsoft Graph accepts the message, then returns the sent message object. There’s no SMTP socket, no XOAUTH2 string, and no host configuration anywhere in the call.
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": "Reaching Out with Nylas", "body": "Reaching out using the <a href='https://www.nylas.com/products/email-api/'>Nylas Email API</a>", "to": [{ "name": "Leyah Miller", "email": "[email protected]" }], "tracking_options": { "opens": true, "links": true, "thread_replies": true, "label": "hey just testing" } }'import Nylas from "nylas";
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",});
async function sendEmail() { try { const sentMessage = await nylas.messages.send({ identifier: "<NYLAS_GRANT_ID>", requestBody: { to: [{ name: "Name", email: "<EMAIL>" }], replyTo: [{ name: "Name", email: "<EMAIL>" }], replyToMessageId: "<MESSAGE_ID>", subject: "Your Subject Here", body: "Your email body here.", }, });
console.log("Sent message:", sentMessage); } catch (error) { console.error("Error sending email:", error); }}
sendEmail();import osimport sysfrom nylas import Clientfrom nylas import utils
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<NYLAS_GRANT_ID>"email = "<EMAIL>"
attachment = utils.file_utils.attach_file_request_builder("Nylas_Logo.png")
message = nylas.messages.send( grant_id, request_body={ "to": [{ "name": "Name", "email": email }], "reply_to": [{ "name": "Name", "email": email }], "reply_to_message_id": "<MESSAGE_ID>", "subject": "Your Subject Here", "body": "Your email body here.", "attachments": [attachment] })
print(message)The API refreshes the OAuth token tied to the grant for you, and the sent message lands in the provider’s Sent folder like any other email the user sends. For the full field reference, see Sending messages with Nylas.
Why you don’t need SMTP
Section titled “Why you don’t need SMTP”The Send API replaces SMTP entirely by talking to each provider’s native send API (Gmail and Microsoft Graph) over HTTPS. That removes 4 hard parts of SMTP delivery: per-provider host setup, OAuth-for-SMTP token encoding, app passwords, and manually copying sent mail into the Sent folder. One request body covers all 6 providers.
The table compares the two approaches. The Send API column is what you get out of the box.
| Concern | SMTP + OAuth (XOAUTH2) | Send API |
|---|---|---|
| Host and port config | Per-provider host, port, and transport security settings | None, one HTTPS endpoint |
| App passwords | Needed when XOAUTH2 isn’t set up; blocked under multi-factor auth | Never used |
| Token refresh | You mint and re-encode the token on every expiry | Refreshed automatically |
| Provider differences | Separate SMTP rules for Gmail, Microsoft, Yahoo, others | One unified request body |
| Sent-folder behavior | You append the message yourself over IMAP | Lands in Sent automatically |
Because the provider sends as the user, deliverability matches native mail and messages thread correctly in the recipient’s client.
Add a reply-to, CC, or attachments
Section titled “Add a reply-to, CC, or attachments”Extend the same send request with cc, bcc, and reply_to (each an array of {name, email} objects), plus attachments for files. Replies route to a different address than the sender when you set reply_to, which is useful when a no-reply identity sends but support should receive responses. These 4 fields are optional and combine freely.
The attachments array carries each file’s base64-encoded content, content_type, and filename. Files up to 3 MB go inline in the JSON body, and larger files up to 25 MB use a multipart request. The snippet below adds a CC, a reply address, and one file to a send.
{ "subject": "Your March invoice", "body": "Your invoice is attached.", "attachments": [ { "filename": "invoice.pdf", "content_type": "application/pdf", "content": "JVBERi0xLjQKJ..." } ]}For the size limits, multipart upload, and inline-image content ID (cid) details, see How to send emails with attachments.
Things to know about sending
Section titled “Things to know about sending”Mail sent through a grant comes from the user’s real address, not a no-reply alias, so replies land back in their inbox and the message threads naturally. That’s the main tradeoff to plan around: every send counts against the provider’s per-user limits. Gmail caps consumer accounts near 500 recipients per day and Workspace accounts near 2,000, and Microsoft applies its own throttling. Plan retries and queueing around those ceilings.
If you send bulk mail to Gmail, the provider’s sending guidelines require authenticated mail and a working one-click unsubscribe header for senders above 5,000 messages a day. Grant sends already pass the provider’s sender authentication checks because they originate from the user’s own mailbox, but you still add the List-Unsubscribe header yourself through custom_headers.
Not every message belongs to a user. Password resets, receipts, and system alerts come from your application, so there’s no mailbox to send from. For those, use transactional send from a domain, which sends from a verified domain with no grant and no OAuth at all.
What’s next
Section titled “What’s next”- Send transactional email from a domain for application mail with no user account
- How to send emails with attachments for the base64, multipart, and inline-image paths
- Connect user accounts with OAuth to create the grant you send through
- Sending messages with Nylas for the full message field reference