Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions examples/email-tracking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Email Tracking with Gmail.js
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add this documentation to a new markdown-file, rather than the existing README with all the other API documentation?


This guide shows you how to track emails that have been sent using Gmail.js.

## Overview

Gmail.js provides a simple API for tracking sent emails through the `gmail.track` namespace. This is perfect for:
- Analytics and monitoring
- Email delivery confirmation
- Logging sent emails
- Building email tracking features

## Basic Usage

### Track Sent Emails

```javascript
const gmail = new Gmail();

// Track all sent emails
gmail.track.sent_emails(function(emailData, url, body, xhr) {
console.log('Email sent!');
console.log('To:', emailData.to);
console.log('Subject:', emailData.subject);
console.log('Body:', emailData.body);
});
```

### Track Scheduled Emails

```javascript
// Track emails scheduled for later
gmail.track.scheduled_emails(function(emailData, url, body, xhr) {
console.log('Email scheduled!');
console.log('To:', emailData.to);
console.log('Scheduled time:', emailData.scheduled_time);
});
```

### Track All Emails (Sent + Scheduled)

```javascript
// Track both sent and scheduled emails
gmail.track.all_emails(function(emailData, type, url, body, xhr) {
if (type === 'sent') {
console.log('Email sent immediately');
} else if (type === 'scheduled') {
console.log('Email scheduled for later');
}

console.log('Email data:', emailData);
});
```

## Email Data Structure

The `emailData` object contains information about the sent email:

```javascript
{
to: ['[email protected]'], // Array of recipients
cc: ['[email protected]'], // CC recipients
bcc: ['[email protected]'], // BCC recipients
subject: 'Email Subject', // Email subject
body: 'Email body content...', // Email body (HTML or plain text)
from: '[email protected]', // Sender email
thread_id: '1234567890abcdef', // Thread ID
message_id: 'msg-a:r123', // Message ID
scheduled_time: 1234567890, // Unix timestamp (for scheduled emails)
// ... additional fields
}
```

## Advanced Examples

### Save Sent Emails to Database

```javascript
gmail.track.sent_emails(function(emailData) {
// Send to your backend API
fetch('/api/emails/track', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
to: emailData.to,
subject: emailData.subject,
sentAt: new Date().toISOString(),
threadId: emailData.thread_id
})
});
});
```

### Email Analytics

```javascript
let emailsSentToday = 0;
let emailsByRecipient = {};

gmail.track.sent_emails(function(emailData) {
emailsSentToday++;

// Track by recipient
emailData.to.forEach(function(recipient) {
emailsByRecipient[recipient] = (emailsByRecipient[recipient] || 0) + 1;
});

console.log('Emails sent today:', emailsSentToday);
console.log('By recipient:', emailsByRecipient);
});
```

### Notification System

```javascript
gmail.track.sent_emails(function(emailData) {
// Show browser notification
if (Notification.permission === 'granted') {
new Notification('Email Sent!', {
body: `To: ${emailData.to.join(', ')}\nSubject: ${emailData.subject}`,
icon: '/path/to/icon.png'
});
}
});
```

### Debug Logger

```javascript
// Use the built-in logger for debugging
gmail.track.sent_emails(gmail.track.logger('MY APP'));

// Or with custom prefix
gmail.track.sent_emails(gmail.track.logger('Email Tracker'));
```

## Use Cases

1. **Email Delivery Confirmation** - Track when emails are successfully sent
2. **Email Analytics Dashboard** - Build analytics for sent emails
3. **Email Backup System** - Automatically backup all sent emails
4. **Compliance and Auditing** - Log all sent emails for compliance
5. **Email Templates Tracking** - Track template usage
6. **A/B Testing** - Track different email variations

## Related

- [Gmail.js Documentation](../README.md)
- [Observer API](../README.md#observers)
- [Issue #646](https://github.com/KartikTalwar/gmail.js/issues/646) - Original feature request
129 changes: 129 additions & 0 deletions src/gmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@
"false": !1,
"null": null
};
each(params.replace(/\+/g, " ").split("&"), function (v, j) {

Check warning on line 752 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (24.x)

'j' is defined but never used

Check warning on line 752 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'j' is defined but never used

Check warning on line 752 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'j' is defined but never used
var param = v.split("="),
key = decodeURIComponent(param[0]),
val,
Expand Down Expand Up @@ -800,7 +800,7 @@
}
};

api.tools.parse_actions = function(params, xhr) {

Check warning on line 803 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (24.x)

'xhr' is defined but never used

Check warning on line 803 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'xhr' is defined but never used

Check warning on line 803 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'xhr' is defined but never used

// upload_attachment event - if found, don"t check other observers. See issue #22
if(params.url.act === "fup" || params.url.act === "fuv" || params.body_is_object) {
Expand Down Expand Up @@ -1918,7 +1918,7 @@
}

let parsedResponse = [];
let originalResponse = response;

Check warning on line 1921 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (24.x)

'originalResponse' is assigned a value but never used

Check warning on line 1921 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'originalResponse' is assigned a value but never used

Check warning on line 1921 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'originalResponse' is assigned a value but never used
try {
// gmail post response structure
// )}]"\n<datalength><rawData>\n<dataLength><rawData>...
Expand Down Expand Up @@ -2097,7 +2097,7 @@
const win = api.helper.get_xhr_window();

api.tools.patch(win.XMLHttpRequest.prototype.open, (orig) => {
win.XMLHttpRequest.prototype.open = function (method, url, async, user, password) {

Check warning on line 2100 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (24.x)

'password' is defined but never used

Check warning on line 2100 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (24.x)

'user' is defined but never used

Check warning on line 2100 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (24.x)

'async' is defined but never used

Check warning on line 2100 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'password' is defined but never used

Check warning on line 2100 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'user' is defined but never used

Check warning on line 2100 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'async' is defined but never used

Check warning on line 2100 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'password' is defined but never used

Check warning on line 2100 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'user' is defined but never used

Check warning on line 2100 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'async' is defined but never used
var out = orig.apply(this, arguments);
this.xhrParams = {
method: method.toString(),
Expand Down Expand Up @@ -2756,7 +2756,7 @@

// observes every element inserted into the DOM by Gmail and looks at the classes on those elements,
// checking for any configured observers related to those classes
api.tools.insertion_observer = function(target, dom_observers, dom_observer_map, sub) {

Check warning on line 2759 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (24.x)

'sub' is defined but never used

Check warning on line 2759 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'sub' is defined but never used

Check warning on line 2759 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'sub' is defined but never used
//console.log("insertion", target, target.className);
if(!dom_observer_map) return;

Expand Down Expand Up @@ -4616,6 +4616,135 @@
api.tools.embedded_data_watcher();
}

/**
* Email tracking helper namespace
* Provides easy-to-use functions for tracking sent emails
*/
api.track = {};

/**
* Track sent emails with a callback function
* This is a convenience wrapper around api.observe.on('send_message')
*
* @param {Function} callback - Function to call when an email is sent
* Receives (emailData, url, body, xhr) as parameters
* @returns {Function} - Returns the callback for potential cleanup
*
* @example
* gmail.track.sent_emails(function(emailData, url, body, xhr) {
* console.log('Email sent!', emailData);
* console.log('To:', emailData.to);
* console.log('Subject:', emailData.subject);
* console.log('Body:', emailData.body);
* });
*/
api.track.sent_emails = function(callback) {
if (typeof callback !== 'function') {
api.tools.error('track.sent_emails requires a callback function');
return null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If people are using our API in a way which can never succeed, we should throw an error, not return null IMO.

This is anyway a kind of API where people are almost guaranteed to not check the return-value (as you yourself are not doing in your examples).

}

// Wrap the callback to provide cleaner parameters
const wrappedCallback = function(url, body, emailData, xhr) {
callback(emailData, url, body, xhr);
};

// Register the observer
api.observe.on('send_message', wrappedCallback);

return wrappedCallback;
};

/**
* Track scheduled emails with a callback function
* This is a convenience wrapper around api.observe.on('send_scheduled_message')
*
* @param {Function} callback - Function to call when an email is scheduled
* Receives (emailData, url, body, xhr) as parameters
* @returns {Function} - Returns the callback for potential cleanup
*
* @example
* gmail.track.scheduled_emails(function(emailData, url, body, xhr) {
* console.log('Email scheduled!', emailData);
* console.log('Scheduled time:', emailData.scheduled_time);
* });
*/
api.track.scheduled_emails = function(callback) {
if (typeof callback !== 'function') {
api.tools.error('track.scheduled_emails requires a callback function');
return null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If people are using our API in a way which can never succeed, we should through, not return null IMO.

This is anyway a kind of API where people are almost guaranteed to not check the return-value (as you yourself are not doing in your examples).

}

// Wrap the callback to provide cleaner parameters
const wrappedCallback = function(url, body, emailData, xhr) {
callback(emailData, url, body, xhr);
};

// Register the observer
api.observe.on('send_scheduled_message', wrappedCallback);

return wrappedCallback;
};

/**
* Track both sent and scheduled emails with a single callback
*
* @param {Function} callback - Function to call when any email is sent or scheduled
* Receives (emailData, type, url, body, xhr) as parameters
* where type is either 'sent' or 'scheduled'
* @returns {Object} - Returns object with both callbacks for potential cleanup
*
* @example
* gmail.track.all_emails(function(emailData, type, url, body, xhr) {
* if (type === 'sent') {
* console.log('Email sent immediately');
* } else if (type === 'scheduled') {
* console.log('Email scheduled for later');
* }
* console.log('Email data:', emailData);
* });
*/
api.track.all_emails = function(callback) {
if (typeof callback !== 'function') {
api.tools.error('track.all_emails requires a callback function');
return null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If people are using our API in a way which can never succeed, we should through, not return null IMO.

This is anyway a kind of API where people are almost guaranteed to not check the return-value (as you yourself are not doing in your examples).

}

const sentCallback = api.track.sent_emails(function(emailData, url, body, xhr) {
callback(emailData, 'sent', url, body, xhr);
});

const scheduledCallback = api.track.scheduled_emails(function(emailData, url, body, xhr) {
callback(emailData, 'scheduled', url, body, xhr);
});

return {
sent: sentCallback,
scheduled: scheduledCallback
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What value does it provide to return these items? If the intent is to allow people to unregister, the API should provide symmetric means of doing so.

};

/**
* Get a simple email tracking logger (for debugging/testing)
* Returns a function that logs email details to console
*
* @param {String} prefix - Optional prefix for console logs
* @returns {Function} - Logger function
*
* @example
* gmail.track.sent_emails(gmail.track.logger('MY APP'));
*/
api.track.logger = function(prefix) {
prefix = prefix || 'Gmail.js';
return function(emailData, url, body, xhr) {

Check warning on line 4739 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (24.x)

'xhr' is defined but never used

Check warning on line 4739 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (24.x)

'body' is defined but never used

Check warning on line 4739 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (24.x)

'url' is defined but never used

Check warning on line 4739 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'xhr' is defined but never used

Check warning on line 4739 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'body' is defined but never used

Check warning on line 4739 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (22.x)

'url' is defined but never used

Check warning on line 4739 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'xhr' is defined but never used

Check warning on line 4739 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'body' is defined but never used

Check warning on line 4739 in src/gmail.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'url' is defined but never used
console.log(prefix + ' - Email sent!');
console.log('To:', emailData.to);
console.log('Subject:', emailData.subject);
console.log('Body preview:', emailData.body ? emailData.body.substring(0, 100) : 'N/A');
console.log('Full data:', emailData);
};
};

return api;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All in all I'm positive to these changes. It makes some functionality more up-front and accessible. That's good!

One thing though, when adding new APIs, make sure to add the type-definitions for those new APIs to gmail.d.ts as well.

We have many typescript users these days 😄

};

Expand Down