惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
小众软件
小众软件
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - Franky
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
Hugging Face - Blog
Hugging Face - Blog
Jina AI
Jina AI
博客园 - 聂微东
S
SegmentFault 最新的问题
量子位
宝玉的分享
宝玉的分享
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园_首页

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Generate email drafts with Nylas Smart Compose
Qasim · 2026-06-23 · via DEV Community

Writing a clear, well-structured email takes time, and it's the kind of task an LLM is genuinely good at. But wiring up your own prompt-to-email pipeline means picking a model, threading the original message in as context, handling streaming, and keeping it all behind your API keys. The Nylas Smart Compose endpoints do that for you: send a natural-language prompt, get back a written message body, and the reply variant pulls in the original email as context automatically.

This post walks through Smart Compose from two angles: the HTTP API for your backend, and the nylas CLI for the terminal. I work on the CLI, so the terminal commands below are the ones I reach for when I'm testing a prompt.

How Smart Compose works

Smart Compose is two endpoints that turn a prompt into a message body. You send a natural-language prompt, and the response comes back with a suggestion field holding the generated text. There's a POST /messages/smart-compose for writing a brand-new message, and a POST /messages/{message_id}/smart-compose for writing a reply, where the original message is folded into the context so the response actually answers it.

The key thing to understand is that Smart Compose generates text, it doesn't send anything. The suggestion it returns is a message body you do something with: pass it straight to the Send Message endpoint, or pre-fill it into a draft for a human to review and edit first. That separation is deliberate, since it lets you put a person between the AI's output and the recipient, which is usually what you want for anything an LLM wrote.

Two things to know before you start. Smart Compose runs against connected OAuth grants only, not Agent Accounts. The prompt also has a ceiling: up to 1,000 tokens, and a longer prompt returns an error.

Generate a new message

To write a fresh email, POST /v3/grants/{grant_id}/messages/smart-compose takes a single prompt describing what you want. The response carries the generated body in suggestion, which you then send or save. The prompt is plain natural language, so "Write an email to Ron with a business brief about our launch" is a valid input.

curl --request POST \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/messages/smart-compose" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{ "prompt": "Write an email to Ron with a business brief about our launch" }'

From the terminal, nylas email smart-compose --prompt generates the draft and prints the suggestion. It's the fastest way to see what a prompt produces before you wire it into an application:

nylas email smart-compose --prompt "Draft a thank you email for the meeting"

The response's suggestion is just text, so the natural next step is to hand it to a send or a draft. Pairing Smart Compose with the Drafts API gives you the review-before-send loop: generate the suggestion, create a draft from it, and let a person approve it before it goes out.

Generate a reply with the thread's context

Replies are where Smart Compose earns its place, because a good reply needs to know what it's answering. POST /v3/grants/{grant_id}/messages/{message_id}/smart-compose takes the same prompt but also the ID of the message you're replying to, and it reads that original message as context. You describe the intent, and the model handles the specifics from the email it's responding to.

curl --request POST \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/messages/<MESSAGE_ID>/smart-compose" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{ "prompt": "Reply to my friend Emily and RSVP yes to the party" }'

The CLI adds the message with --message-id, and passing it switches the command from composing a new message to composing a reply:

nylas email smart-compose \
  --message-id <message-id> \
  --prompt "Reply accepting the invitation"

Because the endpoint reads the original message, your prompt can stay short and high-level. You don't paste the email you're answering into the prompt; you reference it by ID and let Smart Compose pull the context. That keeps your prompt well under the 1,000-token ceiling even when the original message is long.

Build a reply-suggestion loop

Smart Compose fits naturally into an inbound flow. When a message arrives, the message.created webhook delivers the new message, including its ID, which is exactly what the reply endpoint needs. Your handler calls POST /messages/{message_id}/smart-compose with a prompt describing how to respond, gets a suggestion, and creates a draft from it for review.

For a support or sales inbox, this turns every incoming email into a pre-written reply waiting for a person to approve. Your code classifies the message, picks a prompt that matches the situation ("draft a polite reply asking for the order number"), and the reviewer sees a ready draft instead of a blank compose window. The message ID ties the loop together: it identifies the email to respond to and gives Smart Compose its context. When you send the resulting draft, pass that same ID as reply_to_message_id so the reply threads correctly in the recipient's mailbox.

Write prompts that produce usable email

A Smart Compose prompt is an instruction, and the more specific it is, the less editing the suggestion needs. State who the email is to, what it should accomplish, and the tone you want: "Write a brief, friendly email to a customer confirming their refund was processed and will arrive in 5 business days" produces a tighter result than "write a refund email." The model fills in structure and phrasing; your prompt supplies intent and constraints.

For replies, lean on the context the endpoint already has. Because it reads the original message, you don't restate its contents in the prompt, you describe your response to it. "Reply declining the meeting and propose Thursday instead" is enough, since the model already sees the meeting request. Keeping prompts to the intent, and letting the message context carry the details, also keeps you well under the 1,000-token limit.

Stream the response or take it in one piece

Smart Compose offers two ways to receive the generated text, and the right one depends on your interface. To get the whole suggestion back as a single JSON response, the simplest to consume from a backend, add the Accept: application/json header or omit the Accept header entirely, and the endpoint returns one JSON blob with the finished suggestion.

For a user-facing experience, the generation can stream instead. Smart Compose supports Server-Sent Events, so you receive the response token by token as the model produces it, the same typing-out effect a chat interface shows. Streaming makes a noticeable difference in perceived speed, because the user sees text appear immediately rather than waiting for the full message. Either way the content is the same; SSE just changes when the tokens arrive.

The transport is a per-request choice rather than an account setting, so one integration can stream into an interactive compose box and take single-blob responses in a background job, just by switching the header on each call. Latency varies with the length and complexity of the prompt, so whichever transport you pick, plan for a short wait. A "working" indicator in your UI keeps users from wondering whether the request stalled, which matters more with the single-response method where nothing appears until the whole message is ready.

Turn a suggestion into a sent message or draft

A suggestion on its own is just a string; the value comes from what you do with it next. Two patterns cover almost every use. For automated sending where no review is needed, take the suggestion, drop it into the body of a Send Message request, and send it. For anything a human should see first, create a draft from the suggestion so it lands in the user's Drafts folder for editing.

The draft path is the safer default for AI-written mail. A generated message can get a name wrong, strike the wrong tone, or invent a detail, and a draft gives a person the chance to catch that before the recipient does. The flow is: generate the suggestion, create a draft with it, and surface that draft in your UI or the user's mail client for approval. Only on approval does it send.

Because the suggestion is plain text you control, you can also post-process it before it goes anywhere: prepend a greeting, append the user's signature, or run it through your own checks. The endpoint hands you a body, not a finished email, so the last mile stays yours. That review loop is also why Smart Compose returns text rather than sending directly. The endpoint deliberately stops at generation, leaving the decision to send with you, so you can insert exactly as much human oversight as the use case calls for.

Requirements and limits

A few constraints shape where Smart Compose fits. The biggest is the account model: it runs against connected OAuth grants, the mailboxes your users authenticate through Google or Microsoft, and it is not available on Agent Accounts. If you're building on an Agent Account and want AI-written mail, generate the text with your own model and send it through the standard Messages API instead.

The other requirements are about account setup. Smart Compose works on a Google or Microsoft grant connected through an auth app and connector, the standard connected-account flow, and because each call reads the message it's replying to, it needs read scopes like gmail.readonly or Mail.Read. Prompts are capped at 1,000 tokens, which is generous for an instruction but means you reference long source emails by message ID rather than pasting them in.

Things to keep in mind

A few practices make Smart Compose reliable in production rather than just impressive in a demo.

  • Draft, don't auto-send, for anything a person reads. Route the suggestion into a draft for review; AI mail gets names, tone, and facts wrong often enough to warrant a human check.
  • Reference replies by message_id, don't paste the email. The reply endpoint reads the original as context, which keeps your prompt short and under the 1,000-token cap.
  • Stream with SSE for user-facing generation. It shows text immediately; reserve the single-JSON response for backend automation.
  • It generates, it doesn't send. The endpoint returns a body; you choose to send it or draft it.
  • It's connected-grant only. Smart Compose isn't available on Agent Accounts, so plan AI mail there around your own model plus the Messages API.
  • Be specific in prompts. Name the recipient, the goal, and the tone; a precise instruction needs far less editing than a vague one.

Wrapping up

Smart Compose collapses "write an email with AI" into one request: send a prompt, get a suggestion, and send or draft it. The reply endpoint adds the original message as context so your prompt stays a short instruction, and SSE streaming gives a user-facing interface the typing-out effect people expect from AI. The one habit to carry through is routing generated mail into a draft for review, because the endpoint hands you the text precisely so you can decide what happens before it reaches the recipient.

Where to go next: