The standard Send Message endpoint accepts attachments as base64-encoded content inside the JSON body, which caps the entire request at 3 MB (or 25 MB using multipart form data). For files larger than that, use the attachment-uploads API to upload the file directly to Nylas-managed storage, then reference it by ID when you send the message. This flow supports attachments up to 150 MB.
Supported providers: Microsoft (Outlook and Exchange Online via Microsoft Graph).
Google, IMAP, Yahoo, iCloud, and EWS grants are rejected with 401 Unauthorized. For these providers, use the standard attachment flow with its 25 MB multipart limit.
Before you begin
Section titled “Before you begin”You need the following before sending large attachments:
- A Microsoft grant with at least one of these scopes:
Mail.Send,Mail.ReadWrite, orMail.ReadWrite.Shared. See Create an Azure app for how to configure the auth app. - An Exchange Online tenant with message size limits raised to accommodate your largest attachment (see below).
Raise the Exchange Online message size limit
Section titled “Raise the Exchange Online message size limit”Exchange Online defaults to a 35 MB maximum message size. If your attachment plus message overhead exceeds this, the send fails and recipients on Exchange-based mailboxes do not receive the message.
Raise the limit using Exchange Online PowerShell as an Exchange administrator. Set the value to match your expected maximum attachment size. Changes can take up to 60 minutes to propagate.
Per-mailbox (recommended for a targeted rollout):
Organization-wide:
Set-TransportConfig -MaxSendSize 150MB -MaxReceiveSize 150MBHow it works
Section titled “How it works”Instead of embedding the file in the send request, you:
- Create an upload session. Nylas returns a direct-upload URL.
- Upload the file bytes.
PUTthe file directly to the returned URL. No Nylas auth header required. - Complete the session. Nylas verifies the upload and marks the attachment as ready.
- Send the email. Reference the
attachment_idin theattachmentsarray of your send request.
Step 1: Create an upload session
Section titled “Step 1: Create an upload session”Make a Create Upload Session request to start the flow.
curl --request POST \ --url 'https://api.us.nylas.com/v3/grants/<GRANT_ID>/attachment-uploads' \ --header 'Content-Type: application/json' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY_OR_ACCESS_TOKEN>' \ --data-raw '{ "filename": "document.pdf", "content_type": "application/pdf", "size": 1048576 }'{ "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88", "data": { "attachment_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "method": "PUT", "url": "https://storage.googleapis.com/upload/storage/v1/b/BUCKET/o?uploadType=resumable&upload_id=...", "headers": { "Content-Type": "application/pdf" }, "expires_at": "2026-04-22T19:00:00Z", "max_size": 157286400, "size": 5242880, "content_type": "application/pdf", "filename": "quarterly-report.pdf", "grant_id": "abc123" }}| Field | Required | Notes |
|---|---|---|
filename | Yes | Name of the file as it appears in the email. |
content_type | Yes | MIME type (for example, application/pdf, image/png). Any non-empty string is accepted. |
size | No | Expected file size in bytes. Recommended. When provided, Nylas verifies the uploaded bytes match this value during completion. Maximum: 157286400 (150 MB). |
Save the attachment_id from the response. You need it for steps 3 and 4.
Step 2: Upload the file
Section titled “Step 2: Upload the file”PUT the file bytes to the url returned in step 1. This is a Google Cloud Storage resumable upload URL, so no Nylas authorization header is required on this request.
Set Content-Type to match the value you declared in step 1. A successful upload returns HTTP 200 or 201.
curl -X PUT "{url from step 1}" \ -H "Content-Type: application/pdf" \ -H "Content-Length: 5242880" \ --data-binary @quarterly-report.pdfStep 3: Complete the upload session
Section titled “Step 3: Complete the upload session”Once the file is uploaded, notify Nylas so it can verify the upload and mark the attachment ready to send. See the Complete Upload Session reference.
curl --request POST \ --url 'https://api.us.nylas.com/v3/grants/<GRANT_ID>/attachment-uploads/<ATTACHMENT_ID>/complete' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY_OR_ACCESS_TOKEN>'{ "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88", "data": { "attachment_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "grant_id": "abc123", "status": "ready" }}The request body is empty. Once status is ready, the attachment can be referenced in a send request.
Step 4: Send the email
Section titled “Step 4: Send the email”Pass the attachment_id as id inside the attachments array of your Send Message or Create Draft request. Do not include content or content_type. Nylas fetches the file from storage and attaches it at send time.
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 '{ "to": [{ "name": "Jane Smith", "email": "[email protected]" }], "subject": "Q1 Report", "body": "Please find the report attached.", "attachments": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } ] }'If the referenced attachment is not ready at send time (for example, it is still uploading, has failed, or has expired), the send fails. Nylas deletes the draft in both the synchronous and use_draft=true scheduled paths, and the synchronous send returns a 500 to the caller. Verify status with the Get Upload Session endpoint before you send if you aren’t sure the upload completed.
Inline attachments
Section titled “Inline attachments”To embed a large image or file inline in the email body, set content_id on the attachment entry and reference it in the HTML body using a cid: URI.
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 '{ "to": [{ "name": "Jane Smith", "email": "[email protected]" }], "subject": "Q1 Report", "body": "<p>See chart below:</p><img src=\"cid:chart1\">", "attachments": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "content_id": "chart1" } ] }'Nylas treats the attachment as inline when the content_id value appears as cid:{content_id} inside the message body. If you include a content_id but the body does not reference it, the file is still attached but is displayed as a regular attachment rather than inline. See Working with inline attachments for the full rules.
Upload session status
Section titled “Upload session status”Use the Get Upload Session endpoint to check the status of an upload at any time.
| Status | Meaning |
|---|---|
uploading | Session created; Nylas is waiting for the file upload. |
ready | Upload verified and complete. The attachment can be referenced in a send or draft. |
failed | Upload initiation failed (for example, Nylas could not create the storage URL during session creation). Create a new session. |
expired | Session was not completed within 1 hour. Create a new session. |
Errors
Section titled “Errors”All error responses follow the standard Nylas error shape:
{ "request_id": "5fa64c92-e840-4357-86b9-2aa364d35b88", "error": { "type": "invalid_request", "message": "file_size exceeds maximum allowed size of 157286400 bytes" }}| HTTP status | When it occurs |
|---|---|
400 Bad Request | filename or content_type is missing, size is negative or zero, size exceeds 150 MB, or the request body is not valid JSON. |
401 Unauthorized | Grant cannot be resolved, the provider is not Microsoft, the grant lacks the required Microsoft scopes, or the grant_id in the path does not match the session on the GET or complete endpoints. |
404 Not Found | The attachment_id does not exist. |
409 Conflict | /complete was called on a session that is already ready or has failed. |
410 Gone | /complete was called on an expired session, or the session was already past expires_at. |
422 Unprocessable Entity | The file is not in storage, the uploaded size does not match the declared size, or no size was declared and the object is empty. |
500 Internal Server Error | Storage or database failure. Retry the operation. |
Rate limiting is not enforced at the attachment-uploads service itself. Any 429 Too Many Requests response is returned by the upstream API gateway. See Sending errors for general troubleshooting.
Limits and retention
Section titled “Limits and retention”| Parameter | Limit |
|---|---|
| Maximum file size | 150 MB (157286400 bytes) |
| Session expiry | 1 hour from creation |
| Supported providers | Microsoft (Graph) |
| Exchange Online default message size | 35 MB. Raise via PowerShell (see Before you begin). |
File retention after ready | Until expires_at (1 hour after creation). Send before the window closes. |
| Metadata retention | 60 days. You can call GET /attachment-uploads/{attachment_id} for observability after the file is gone. |
Related
Section titled “Related”- Using the Attachments API. Standard attachment flow (up to 3 MB JSON or 25 MB multipart), inline attachments, and downloading attachments.
- Sending messages with Nylas. End-to-end guide to sending and scheduling messages.
- Sending errors. Troubleshooting delivery failures.
- Create an Azure app. Set up the Microsoft auth app and scopes needed to authenticate a grant.
- API reference: Create an upload session, Get upload session, Complete upload session.