A sales rep sends a follow-up to a prospect and wants to know the moment it lands and gets read, so they can call while the offer is top of mind. That signal, “your email was just opened,” is what powers outreach sequences, deal-stage automation, and reply-based routing. You get it by turning on tracking when you send a message and listening for the webhook that fires when the recipient acts.
Enable tracking when you send
Section titled “Enable tracking when you send”Opens, clicks, and replies are only reported for messages you send with tracking turned on, so the tracking starts at send time, not after. Pass a tracking_options object on the Send Message request with the booleans opens, links, and thread_replies, plus an optional label that’s echoed back in every notification.
The request below sends an HTML message with all three signals enabled. The label is a free-text tag you read later to match a notification back to a campaign or contact. Message tracking needs a production application; trial accounts return “Tracking options are not allowed for trial accounts”.
curl --request POST \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/send' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --data-raw '{ "subject": "Quick follow-up on your trial", "body": "Thanks for trying us out. Reply or <a href=\"https://example.com/demo\">book a demo</a> when ready.", "to": [{ "name": "Kim Townsend", "email": "[email protected]" }], "tracking_options": { "opens": true, "links": true, "thread_replies": true, "label": "trial-followup-q2" } }'const Nylas = require("nylas").default;
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>" });
const message = await nylas.messages.send({ identifier: "<NYLAS_GRANT_ID>", requestBody: { subject: "Quick follow-up on your trial", body: 'Thanks for trying us out. Reply or <a href="https://example.com/demo">book a demo</a> when ready.', trackingOptions: { opens: true, links: true, threadReplies: true, label: "trial-followup-q2", }, },});
console.log(`Sent message ${message.data.id}`);from nylas import Client
nylas = Client(api_key="<NYLAS_API_KEY>")
message = nylas.messages.send( identifier="<NYLAS_GRANT_ID>", request_body={ "subject": "Quick follow-up on your trial", "body": 'Thanks for trying us out. Reply or <a href="https://example.com/demo">book a demo</a> when ready.', "tracking_options": { "opens": True, "links": True, "thread_replies": True, "label": "trial-followup-q2", }, },)
print(f"Sent message {message.data.id}")These three flags work the same way whether the grant is Google, Microsoft, or any other provider you send through, so one send path covers your whole sender base.
Subscribe to open and reply events
Section titled “Subscribe to open and reply events”The send request enables tracking, but the events arrive over webhooks, so you need a destination subscribed to the right triggers before they can reach you. Three triggers report engagement: message.opened for opens, message.link_clicked for clicked links, and thread.replied for replies. You can subscribe to all three on a single webhook URL, so one endpoint receives every tracking event.
Create the destination with a POST /v3/webhooks/, passing your HTTPS endpoint as webhook_url and the triggers in trigger_types. The request below subscribes one endpoint to all three tracking events. For the full create flow with Node.js, Python, Ruby, Java, and Kotlin, see Using webhooks with Nylas.
curl --request POST \ --url 'https://api.us.nylas.com/v3/webhooks/' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --data-raw '{ "trigger_types": ["message.opened", "message.link_clicked", "thread.replied"], "webhook_url": "https://yourapp.com/webhooks/nylas", "description": "Email engagement tracking" }'When you create the webhook, Nylas sends a GET with a challenge parameter that your endpoint must echo back to finish activation. For real-time setup across all the message and event triggers, see Get real-time updates with webhooks.
Handle a tracking notification
Section titled “Handle a tracking notification”Each notification names the message it concerns, so your handler can match the event back to the contact you tracked without storing anything extra. The payload includes the tracked message’s ID, the label you set at send time, and a recents array holding the last 50 events for that message, each stamped with a timestamp, IP, and user agent. Your endpoint should return 200 OK within 10 seconds so the API doesn’t count the delivery as failed and retry it.
The handler below reads the trigger type and the message ID, then routes on the event. Open events depend on a tracking pixel that the recipient’s client loads, so a client that blocks remote images never fires message.opened, even when the message was read. Treat opens as a soft signal and lean on clicks and replies for harder proof.
from flask import Flask, request
app = Flask(__name__)
@app.route("/webhooks/nylas", methods=["GET", "POST"])def nylas_webhook(): if request.method == "GET": return request.args.get("challenge", ""), 200
event = request.get_json() trigger = event["type"] message_id = event["data"]["object"]["message_id"]
if trigger == "message.opened": print(f"Opened: {message_id}") elif trigger == "message.link_clicked": print(f"Link clicked: {message_id}") elif trigger == "thread.replied": print(f"Thread replied: {message_id}")
return "", 200Things to know about email tracking
Section titled “Things to know about email tracking”Open tracking is the least reliable of the three signals, so design your logic around its blind spots rather than trusting raw open counts. Nylas records an open when the recipient’s client loads a transparent one-pixel image embedded in the message. Corporate gateways and privacy-focused clients block remote images by default, which silently drops the event, and prefetching proxies do the opposite by loading the pixel before a human reads anything.
Apple Mail Privacy Protection, on by default since iOS 15, routes images through Apple’s proxy and pre-loads them, registering an open whether or not the recipient saw the message. Apple’s share of the email client market sits near 50%, so a large slice of your open data is inflated. The practical takeaway: report opens as “opened at least once,” never as a unique read count, and never gate revenue logic on an open alone.
Click tracking is sturdier because it records a real human action. When you set links: true, Nylas rewrites every valid HTML anchor in the body to a tracking URL, logs the click, then forwards the recipient to the original destination. It rewrites links across the whole message rather than per-link, caps at 100 tracked links, and skips URLs that carry login credentials so it doesn’t break authenticated destinations. Reply tracking via thread.replied is the strongest signal of the three, since a reply is unambiguous intent.
Each trigger also ships a .legacy variant (message.opened.legacy, message.link_clicked.legacy, thread.replied.legacy) for applications still on the older notification format. New integrations should subscribe to the non-legacy triggers shown above. Because tracking rewrites message content and pixels recipients, be transparent: honor consent, disclose tracking where your jurisdiction requires it, and don’t track links that carry sensitive data. For the full field reference and per-trigger payloads, see tracking messages.
What’s next
Section titled “What’s next”- Tracking messages for the full tracking reference, scopes, and per-provider behavior
- Get real-time updates with webhooks to subscribe one endpoint to messages, opens, replies, and calendar events
- Using webhooks with Nylas for webhook setup, signature verification, and failure handling
- Webhook notification schemas for every trigger’s payload shape