Skip to content

Commit fabe2d6

Browse files
authored
NYL-22: Add Contact metadata and webhook parity (#759)
1 parent 2bdf4ae commit fabe2d6

6 files changed

Lines changed: 62 additions & 1 deletion

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111
- Add optional `trackingOptions.domainName` support for custom link and open tracking hostnames
12+
- Add Contact metadata request/response types and `metadataPair` filtering, with contact webhook compatibility documentation
1213

1314
### Changed
1415
- Clarify that event `default` visibility is Google-only

‎src/models/contacts.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ export interface Contact {
1515
givenName?: string;
1616
jobTitle?: string;
1717
managerName?: string;
18+
/**
19+
* Nylas-owned metadata associated with this contact. Metadata is not written
20+
* to the provider and does not follow a contact if its public ID changes.
21+
*/
22+
metadata?: Record<string, string>;
1823
middleName?: string;
1924
nickname?: string;
2025
notes?: string;
@@ -118,6 +123,12 @@ export interface ListContactQueryParams extends ListQueryParams {
118123
* When set to true, returns the contacts also within the specified Contact Group subgroups, if the group parameter is set.
119124
*/
120125
recurse?: boolean;
126+
/**
127+
* Filters contacts by one indexed metadata entry in `key:value` form.
128+
* Use one of `key1` through `key5`. This filter cannot be combined with
129+
* provider-side contact filters; pagination parameters are supported.
130+
*/
131+
metadataPair?: Record<string, string>;
121132
}
122133

123134
/**
@@ -139,6 +150,11 @@ export type CreateContactRequest = {
139150
imAddresses?: InstantMessagingAddress[];
140151
jobTitle?: string;
141152
managerName?: string;
153+
/**
154+
* Nylas-owned metadata for the contact. On update, omission or `null`
155+
* preserves existing metadata, an object replaces it, and `{}` clears it.
156+
*/
157+
metadata?: Record<string, string> | null;
142158
middleName?: string;
143159
nickname?: string;
144160
notes?: string;

‎src/models/webhooks.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ export enum WebhookTriggers {
171171
FolderUpdated = 'folder.updated',
172172
FolderDeleted = 'folder.deleted',
173173

174-
// Contact triggers
174+
// Contact triggers. Native iCloud supports both triggers. Yahoo supports
175+
// neither trigger, including for contact changes made through the Nylas API.
175176
ContactUpdated = 'contact.updated',
176177
ContactDeleted = 'contact.deleted',
177178

‎tests/apiClient.spec.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,22 @@ describe('APIClient', () => {
105105
);
106106
});
107107

108+
it('should serialize a contact metadata filter to the wire format', () => {
109+
const options = client.requestOptions({
110+
path: '/v3/grants/id123/contacts',
111+
method: 'GET',
112+
queryParams: {
113+
metadataPair: { key1: 'sync_eligible' },
114+
},
115+
});
116+
117+
expect(options.url).toEqual(
118+
new URL(
119+
'https://api.us.nylas.com/v3/grants/id123/contacts?metadata_pair=key1%3Async_eligible'
120+
)
121+
);
122+
});
123+
108124
it('should handle all the different types of query params', () => {
109125
const options = client.requestOptions({
110126
path: '/test',

‎tests/resources/contacts.spec.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@ describe('Contacts', () => {
4444
});
4545
});
4646

47+
it('should pass an indexed metadata filter to the API', async () => {
48+
await contacts.list({
49+
identifier: 'id123',
50+
queryParams: {
51+
metadataPair: { key1: 'sync_eligible' },
52+
},
53+
});
54+
55+
expect(apiClient.request).toHaveBeenCalledWith({
56+
method: 'GET',
57+
path: '/v3/grants/id123/contacts',
58+
queryParams: {
59+
metadataPair: { key1: 'sync_eligible' },
60+
},
61+
});
62+
});
63+
4764
it('should paginate correctly if a nextCursor is present', async () => {
4865
apiClient.request.mockResolvedValueOnce({
4966
requestId: 'request123',
@@ -199,6 +216,7 @@ describe('Contacts', () => {
199216
},
200217
],
201218
givenName: 'Test',
219+
metadata: { key1: 'sync_eligible' },
202220
},
203221
overrides: {
204222
apiUri: 'https://test.api.nylas.com',
@@ -220,6 +238,7 @@ describe('Contacts', () => {
220238
},
221239
],
222240
givenName: 'Test',
241+
metadata: { key1: 'sync_eligible' },
223242
},
224243
overrides: {
225244
apiUri: 'https://test.api.nylas.com',
@@ -236,6 +255,7 @@ describe('Contacts', () => {
236255
contactId: 'contact123',
237256
requestBody: {
238257
birthday: '1960-12-31',
258+
metadata: {},
239259
},
240260
overrides: {
241261
apiUri: 'https://test.api.nylas.com',
@@ -248,6 +268,7 @@ describe('Contacts', () => {
248268
path: '/v3/grants/id123/contacts/contact123',
249269
body: {
250270
birthday: '1960-12-31',
271+
metadata: {},
251272
},
252273
overrides: {
253274
apiUri: 'https://test.api.nylas.com',

‎tests/resources/webhooks.spec.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,8 @@ describe('Webhooks', () => {
317317
['MessageTransactionalBounced', 'message.transactional.bounced'],
318318
['MessageTransactionalComplaint', 'message.transactional.complaint'],
319319
['MessageTransactionalRejected', 'message.transactional.rejected'],
320+
['ContactUpdated', 'contact.updated'],
321+
['ContactDeleted', 'contact.deleted'],
320322
['NotetakerCreated', 'notetaker.created'],
321323
['NotetakerUpdated', 'notetaker.updated'],
322324
['NotetakerDeleted', 'notetaker.deleted'],
@@ -334,6 +336,10 @@ describe('Webhooks', () => {
334336
'message.updated.truncated'
335337
);
336338
});
339+
340+
it('should not expose an unsupported contact.created trigger', () => {
341+
expect(Object.values(WebhookTriggers)).not.toContain('contact.created');
342+
});
337343
});
338344

339345
describe('extractChallengeParameter', () => {

0 commit comments

Comments
 (0)