DEV Community

Qasim Muhammad
Qasim Muhammad

Posted on

Send Email Without SMTP Configuration

This is a complete, authenticated email send — no SMTP host, no port, no XOAUTH2 string anywhere:

curl --request POST \
  --url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/messages/send" \
  --header "Authorization: Bearer <NYLAS_API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{
    "to": [{ "name": "Kim Townsend", "email": "kim@example.com" }],
    "subject": "Your March invoice",
    "body": "<p>Your invoice is attached below.</p>"
  }'
Enter fullscreen mode Exit fullscreen mode

The call is synchronous: it blocks until the mail system accepts the message, then returns the sent message object. The grant_id identifies whose mailbox you're sending through, and that's the entire configuration surface.

What that one request replaces

If you've wired SMTP against modern providers recently, you know the drill. Google and Microsoft both turned off basic password auth, so SMTP now wants XOAUTH2: mint an OAuth token, base64-encode it into the SMTP AUTH string, refresh it on every expiry. App passwords are the usual fallback — except they're blocked under enforced multi-factor authentication. And each provider has its own host, port, and transport security rules to keep straight.

The send-without-SMTP recipe tallies it as four hard parts eliminated. Side by side:

Concern SMTP + OAuth (XOAUTH2) Send API
Host and port config Per-provider host, port, transport security None — one HTTPS endpoint
App passwords Needed when XOAUTH2 isn't set up; blocked under MFA Never used
Token refresh You mint and re-encode the token on every expiry Refreshed automatically
Provider differences Separate SMTP rules per provider One unified request body
Sent-folder behavior You append the message yourself over IMAP Lands in Sent automatically

One request body covers all 6 supported providers, and because the provider sends as the user, deliverability matches native mail and messages thread correctly in the recipient's client.

Extras bolt onto the same body. cc, bcc, and reply_to are arrays of {name, email} objects — reply_to is handy when one identity sends but support should receive the responses. Attachments ride along base64-encoded: files up to 3 MB go inline in the JSON, larger files up to 25 MB use a multipart request.

{
  "to": [{ "name": "Kim Townsend", "email": "kim@example.com" }],
  "cc": [{ "name": "Kaveh", "email": "kaveh@example.com" }],
  "reply_to": [{ "name": "Support", "email": "support@example.com" }],
  "subject": "Your March invoice",
  "body": "Your invoice is attached.",
  "attachments": [
    {
      "filename": "invoice.pdf",
      "content_type": "application/pdf",
      "content": "JVBERi0xLjQKJ..."
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

These fields combine freely — the example above is still the same single POST to /messages/send.

The fine print on sending through user mailboxes

When the grant is a connected user account, sends come from that user's real address — which is exactly why deliverability matches native mail and replies thread correctly. The tradeoff: every send counts against the provider's per-user ceilings. Gmail caps consumer accounts near 500 recipients per day and Workspace accounts near 2,000; Microsoft applies its own throttling. Bulk senders pushing past 5,000 messages a day to Gmail also need a working List-Unsubscribe header, which you add via custom_headers.

Plan queueing and retries around those numbers, or — if the mail belongs to your application rather than a user — don't send through a human's mailbox at all.

When the app should own the mailbox

Password resets, agent-generated replies, scheduling confirmations: there's no user mailbox these naturally belong to. For that case there's a mailbox your application owns outright — a Nylas Agent Account, currently in beta. It's a real hosted address (agent@yourdomain.com) created entirely through the API, and outbound mail uses the exact same /messages/send call as above. Same HTTP-only sending, no OAuth consent screen, no human inbox involved.

So is SMTP dead? Not quite

Here's the twist: after spending half a post celebrating SMTP's absence, the cases where you want protocol access are real — they're just different cases. They're about humans and existing software, not your application code:

  • A human supervisor wants to read and answer the agent's mail from Outlook or Apple Mail.
  • A legacy system only knows how to speak SMTP.
  • You want to debug a mailbox with a regular client instead of curl.

Agent Accounts support this as an opt-in layer. Set an app_password on the grant (18–40 characters, mixed case plus a digit, printable ASCII) and the mailbox accepts standard IMAP and SMTP submission: IMAP on port 993 with implicit TLS, SMTP on 465 (implicit TLS) or 587 (STARTTLS). Nylas validates the password on write and stores only a bcrypt hash — you can't retrieve it later, only reset it. Without an app_password set, both protocols reject authentication outright, so protocol access stays off until you deliberately turn it on. Clients stay live through IMAP IDLE (push-style updates, with a 30-minute IDLE timeout the client re-issues automatically), so the human supervisor sees new mail without polling.

The part that makes the hybrid work: both surfaces are the same mailbox. A message sent via SMTP submission appears in the API and fires the same message.created webhook as an API send; an API send shows up in the client's Sent folder. Flag changes sync within seconds in both directions, and outbound mail is capped at 40 MB whichever path it takes. There's no "protocol traffic" vs. "API traffic" split to reason about — your automation and your human reviewer are looking at one source of truth.

Picking your path

The decision tree is shorter than it looks:

  1. Mail belongs to a user? Send through their connected grant — HTTP call, their address, their Sent folder.
  2. Mail belongs to your app or agent? Send from an Agent Account the app owns — same HTTP call, your address.
  3. A human or legacy tool needs to touch the mailbox directly? Add an app_password and hand them IMAP/SMTP settings.

In all three, your application code never opens an SMTP socket.

Try the two-minute version: take the curl at the top of this post, point it at a grant, and send yourself a message. Then ask which of your current systems still maintain SMTP configs — and whether any of them have a reason to.

Top comments (0)