@trycourier/courier-js 1.4.2 → 2.0.1-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/brand-client.test.d.ts +1 -0
- package/dist/__tests__/courier-client.test.d.ts +1 -0
- package/dist/__tests__/inbox-client.test.d.ts +1 -0
- package/dist/__tests__/lists-client.test.d.ts +1 -0
- package/dist/__tests__/preferences-client.test.d.ts +1 -0
- package/dist/__tests__/shared-instance.test.d.ts +1 -0
- package/dist/__tests__/token-client.test.d.ts +1 -0
- package/dist/__tests__/tracking-client.test.d.ts +1 -0
- package/dist/__tests__/utils.d.ts +2 -0
- package/dist/client/brand-client.d.ts +12 -0
- package/dist/client/client.d.ts +5 -0
- package/dist/client/courier-client.d.ts +38 -0
- package/dist/client/inbox-client.d.ts +80 -0
- package/dist/client/list-client.d.ts +21 -0
- package/dist/client/preference-client.d.ts +46 -0
- package/dist/client/token-client.d.ts +24 -0
- package/dist/client/tracking-client.d.ts +32 -0
- package/dist/index.d.ts +19 -40
- package/dist/index.js +1 -189
- package/dist/index.mjs +1028 -133
- package/dist/jest.setup.d.ts +0 -0
- package/dist/shared/authentication-listener.d.ts +9 -0
- package/dist/shared/courier.d.ts +68 -0
- package/dist/socket/courier-socket.d.ts +24 -0
- package/dist/socket/inbox-socket.d.ts +19 -0
- package/dist/types/brands.d.ts +36 -0
- package/dist/types/courier-api-urls.d.ts +11 -0
- package/dist/types/inbox.d.ts +43 -0
- package/dist/types/pagination.d.ts +4 -0
- package/dist/types/preference.d.ts +37 -0
- package/dist/types/token.d.ts +12 -0
- package/dist/types/tracking-event.d.ts +1 -0
- package/dist/utils/coding.d.ts +2 -0
- package/dist/utils/logger.d.ts +10 -0
- package/dist/utils/request.d.ts +21 -0
- package/dist/utils/uuid.d.ts +3 -0
- package/package.json +32 -21
- package/README.md +0 -123
|
File without changes
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { CourierClient, CourierProps } from '../client/courier-client';
|
|
2
|
+
import { AuthenticationListener } from '../shared/authentication-listener';
|
|
3
|
+
/**
|
|
4
|
+
* Courier is a singleton class that manages a shared Courier client instance and other resources.
|
|
5
|
+
* UI components will automatically syncronize with this instance.
|
|
6
|
+
* If you only need to call the Courier api, you should consider using the CourierClient directly.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Courier {
|
|
9
|
+
/**
|
|
10
|
+
* The unique identifier for the Courier instance
|
|
11
|
+
*/
|
|
12
|
+
readonly id: string;
|
|
13
|
+
/**
|
|
14
|
+
* The shared Courier instance
|
|
15
|
+
*/
|
|
16
|
+
private static instance;
|
|
17
|
+
/**
|
|
18
|
+
* The Courier client instance
|
|
19
|
+
*/
|
|
20
|
+
private instanceClient?;
|
|
21
|
+
/**
|
|
22
|
+
* The pagination limit (min: 1, max: 100)
|
|
23
|
+
*/
|
|
24
|
+
private _paginationLimit;
|
|
25
|
+
get paginationLimit(): number;
|
|
26
|
+
set paginationLimit(value: number);
|
|
27
|
+
/**
|
|
28
|
+
* Get the Courier client instance
|
|
29
|
+
* @returns The Courier client instance or undefined if not signed in
|
|
30
|
+
*/
|
|
31
|
+
get client(): CourierClient | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The authentication listeners
|
|
34
|
+
*/
|
|
35
|
+
private authenticationListeners;
|
|
36
|
+
/**
|
|
37
|
+
* Get the shared Courier instance
|
|
38
|
+
* @returns The shared Courier instance
|
|
39
|
+
*/
|
|
40
|
+
static get shared(): Courier;
|
|
41
|
+
/**
|
|
42
|
+
* Sign in to Courier
|
|
43
|
+
* @param options - The options for the Courier client
|
|
44
|
+
*/
|
|
45
|
+
signIn(props: CourierProps): void;
|
|
46
|
+
/**
|
|
47
|
+
* Sign out of Courier
|
|
48
|
+
*/
|
|
49
|
+
signOut(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Register a callback to be notified of authentication state changes
|
|
52
|
+
* @param callback - Function to be called when authentication state changes
|
|
53
|
+
* @returns AuthenticationListener instance that can be used to remove the listener
|
|
54
|
+
*/
|
|
55
|
+
addAuthenticationListener(callback: (props: {
|
|
56
|
+
userId?: string;
|
|
57
|
+
}) => void): AuthenticationListener;
|
|
58
|
+
/**
|
|
59
|
+
* Unregister an authentication state change listener
|
|
60
|
+
* @param listener - The AuthenticationListener instance to remove
|
|
61
|
+
*/
|
|
62
|
+
removeAuthenticationListener(listener: AuthenticationListener): void;
|
|
63
|
+
/**
|
|
64
|
+
* Notify all authentication listeners
|
|
65
|
+
* @param props - The props to notify the listeners with
|
|
66
|
+
*/
|
|
67
|
+
private notifyAuthenticationListeners;
|
|
68
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CourierClientOptions } from '../client/courier-client';
|
|
2
|
+
export declare class CourierSocket {
|
|
3
|
+
private static readonly NORMAL_CLOSURE_STATUS;
|
|
4
|
+
private webSocket;
|
|
5
|
+
private pingInterval;
|
|
6
|
+
onOpen?: () => void;
|
|
7
|
+
onMessageReceived?: (message: string) => void;
|
|
8
|
+
onClose?: (code: number, reason?: string) => void;
|
|
9
|
+
onError?: (error: Error) => void;
|
|
10
|
+
private readonly url;
|
|
11
|
+
readonly options: CourierClientOptions;
|
|
12
|
+
constructor(url: string, options: CourierClientOptions);
|
|
13
|
+
/**
|
|
14
|
+
* Dynamically checks if the WebSocket is connected
|
|
15
|
+
*/
|
|
16
|
+
get isConnected(): boolean;
|
|
17
|
+
connect(): Promise<void>;
|
|
18
|
+
disconnect(): void;
|
|
19
|
+
send(message: Record<string, any>): Promise<boolean>;
|
|
20
|
+
keepAlive(props?: {
|
|
21
|
+
intervalInMillis?: number;
|
|
22
|
+
}): void;
|
|
23
|
+
private stopPing;
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CourierSocket } from './courier-socket';
|
|
2
|
+
import { CourierClientOptions } from '../client/courier-client';
|
|
3
|
+
import { InboxMessage } from '../types/inbox';
|
|
4
|
+
export interface MessageEvent {
|
|
5
|
+
event: EventType;
|
|
6
|
+
messageId?: string;
|
|
7
|
+
type: string;
|
|
8
|
+
}
|
|
9
|
+
export type EventType = 'read' | 'unread' | 'mark-all-read' | 'opened' | 'unopened' | 'archive' | 'unarchive' | 'click';
|
|
10
|
+
export declare class InboxSocket extends CourierSocket {
|
|
11
|
+
receivedMessage?: (message: InboxMessage) => void;
|
|
12
|
+
receivedMessageEvent?: (event: MessageEvent) => void;
|
|
13
|
+
constructor(options: CourierClientOptions);
|
|
14
|
+
private convertToType;
|
|
15
|
+
sendSubscribe(props?: {
|
|
16
|
+
version?: number;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
private static buildUrl;
|
|
19
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface CourierBrand {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
created: number;
|
|
5
|
+
updated: number;
|
|
6
|
+
published: number;
|
|
7
|
+
version: string;
|
|
8
|
+
settings?: CourierBrandSettings;
|
|
9
|
+
}
|
|
10
|
+
export interface CourierBrandSettings {
|
|
11
|
+
colors?: CourierBrandColors;
|
|
12
|
+
email?: CourierBrandEmail;
|
|
13
|
+
inapp?: CourierBrandInApp;
|
|
14
|
+
}
|
|
15
|
+
export interface CourierBrandColors {
|
|
16
|
+
primary?: string;
|
|
17
|
+
secondary?: string;
|
|
18
|
+
tertiary?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface CourierBrandEmail {
|
|
21
|
+
header?: {
|
|
22
|
+
barColor?: string;
|
|
23
|
+
};
|
|
24
|
+
footer?: {
|
|
25
|
+
markdown?: string;
|
|
26
|
+
};
|
|
27
|
+
head?: {
|
|
28
|
+
inheritDefault?: boolean;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface CourierBrandInApp {
|
|
32
|
+
borderRadius?: string;
|
|
33
|
+
disableMessageIcon?: boolean;
|
|
34
|
+
placement?: string;
|
|
35
|
+
disableCourierFooter?: boolean;
|
|
36
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface CourierGetInboxMessagesResponse {
|
|
2
|
+
data?: {
|
|
3
|
+
count?: number;
|
|
4
|
+
messages?: {
|
|
5
|
+
pageInfo?: {
|
|
6
|
+
startCursor?: string;
|
|
7
|
+
hasNextPage?: boolean;
|
|
8
|
+
};
|
|
9
|
+
nodes?: InboxMessage[];
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface InboxAction {
|
|
14
|
+
content?: string;
|
|
15
|
+
href?: string;
|
|
16
|
+
data?: Record<string, any>;
|
|
17
|
+
background_color?: string;
|
|
18
|
+
style?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface InboxMessage {
|
|
21
|
+
messageId: string;
|
|
22
|
+
title?: string;
|
|
23
|
+
body?: string;
|
|
24
|
+
preview?: string;
|
|
25
|
+
actions?: InboxAction[];
|
|
26
|
+
data?: Record<string, any>;
|
|
27
|
+
created?: string;
|
|
28
|
+
archived?: string;
|
|
29
|
+
read?: string;
|
|
30
|
+
opened?: string;
|
|
31
|
+
tags?: string[];
|
|
32
|
+
trackingIds?: {
|
|
33
|
+
archiveTrackingId?: string;
|
|
34
|
+
openTrackingId?: string;
|
|
35
|
+
clickTrackingId?: string;
|
|
36
|
+
deliverTrackingId?: string;
|
|
37
|
+
unreadTrackingId?: string;
|
|
38
|
+
readTrackingId?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface CourierGetInboxMessageResponse {
|
|
42
|
+
message: InboxMessage;
|
|
43
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type CourierUserPreferencesStatus = 'OPTED_IN' | 'OPTED_OUT' | 'REQUIRED' | 'UNKNOWN';
|
|
2
|
+
export type CourierUserPreferencesChannel = 'direct_message' | 'email' | 'push' | 'sms' | 'webhook' | 'unknown';
|
|
3
|
+
export interface CourierUserPreferencesPaging {
|
|
4
|
+
cursor?: string;
|
|
5
|
+
more: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface CourierUserPreferencesTopic {
|
|
8
|
+
topicId: string;
|
|
9
|
+
topicName: string;
|
|
10
|
+
sectionId: string;
|
|
11
|
+
sectionName: string;
|
|
12
|
+
status: CourierUserPreferencesStatus;
|
|
13
|
+
defaultStatus: CourierUserPreferencesStatus;
|
|
14
|
+
hasCustomRouting: boolean;
|
|
15
|
+
customRouting: CourierUserPreferencesChannel[];
|
|
16
|
+
}
|
|
17
|
+
export interface CourierUserPreferences {
|
|
18
|
+
items: CourierUserPreferencesTopic[];
|
|
19
|
+
paging: CourierUserPreferencesPaging;
|
|
20
|
+
}
|
|
21
|
+
export interface CourierUserPreferencesTopicResponse {
|
|
22
|
+
topic: CourierUserPreferencesTopic;
|
|
23
|
+
}
|
|
24
|
+
export declare class PreferenceTransformer {
|
|
25
|
+
/**
|
|
26
|
+
* Transforms a single API response item to the CourierUserPreferencesTopic type
|
|
27
|
+
* @param item - The API response item
|
|
28
|
+
* @returns A CourierUserPreferencesTopic object
|
|
29
|
+
*/
|
|
30
|
+
transformItem(item: any): CourierUserPreferencesTopic;
|
|
31
|
+
/**
|
|
32
|
+
* Transforms an array of API response items to CourierUserPreferencesTopic objects
|
|
33
|
+
* @param items - The API response items
|
|
34
|
+
* @returns A generator of CourierUserPreferencesTopic objects
|
|
35
|
+
*/
|
|
36
|
+
transform(items: any[]): Generator<CourierUserPreferencesTopic>;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type CourierTrackingEvent = 'CLICKED' | 'DELIVERED' | 'OPENED' | 'READ' | 'UNREAD';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class Logger {
|
|
2
|
+
private readonly showLogs;
|
|
3
|
+
private readonly PREFIX;
|
|
4
|
+
constructor(showLogs: boolean);
|
|
5
|
+
warn(message: string, ...args: any[]): void;
|
|
6
|
+
log(message: string, ...args: any[]): void;
|
|
7
|
+
error(message: string, ...args: any[]): void;
|
|
8
|
+
debug(message: string, ...args: any[]): void;
|
|
9
|
+
info(message: string, ...args: any[]): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CourierClientOptions } from '../client/courier-client';
|
|
2
|
+
export declare class CourierRequestError extends Error {
|
|
3
|
+
code: number;
|
|
4
|
+
type?: string | undefined;
|
|
5
|
+
constructor(code: number, message: string, type?: string | undefined);
|
|
6
|
+
}
|
|
7
|
+
export declare function http(props: {
|
|
8
|
+
url: string;
|
|
9
|
+
options: CourierClientOptions;
|
|
10
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
body?: any;
|
|
13
|
+
validCodes?: number[];
|
|
14
|
+
}): Promise<any>;
|
|
15
|
+
export declare function graphql(props: {
|
|
16
|
+
url: string;
|
|
17
|
+
options: CourierClientOptions;
|
|
18
|
+
headers: Record<string, string>;
|
|
19
|
+
query: string;
|
|
20
|
+
variables?: Record<string, any>;
|
|
21
|
+
}): Promise<any>;
|
package/package.json
CHANGED
|
@@ -1,28 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trycourier/courier-js",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
3
|
+
"version": "2.0.1-beta",
|
|
4
|
+
"description": "A browser-safe API wrapper",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "vite",
|
|
10
|
+
"build": "vite build",
|
|
11
|
+
"watch": "vite build --watch",
|
|
12
|
+
"preview": "vite preview",
|
|
13
|
+
"test": "jest",
|
|
14
|
+
"prepare": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"courier",
|
|
18
|
+
"api",
|
|
19
|
+
"wrapper",
|
|
20
|
+
"typescript",
|
|
21
|
+
"browser"
|
|
22
|
+
],
|
|
23
|
+
"author": "Courier",
|
|
8
24
|
"license": "MIT",
|
|
9
25
|
"files": [
|
|
10
|
-
"dist
|
|
26
|
+
"dist"
|
|
11
27
|
],
|
|
12
28
|
"devDependencies": {
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
24
|
-
"dev": "tsup src/index.ts --format esm,cjs --watch --dts",
|
|
25
|
-
"lint": "eslint \"src/**/*.ts*\"",
|
|
26
|
-
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist"
|
|
29
|
+
"@types/jest": "29.5.14",
|
|
30
|
+
"dotenv": "16.4.7",
|
|
31
|
+
"jest": "29.7.0",
|
|
32
|
+
"jest-environment-jsdom": "29.7.0",
|
|
33
|
+
"jest-fetch-mock": "^3.0.3",
|
|
34
|
+
"terser": "5.39.0",
|
|
35
|
+
"ts-jest": "29.1.1",
|
|
36
|
+
"vite": "6.2.6",
|
|
37
|
+
"vite-plugin-dts": "4.5.3"
|
|
27
38
|
}
|
|
28
|
-
}
|
|
39
|
+
}
|
package/README.md
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
[](https://courier.com)
|
|
2
|
-
|
|
3
|
-
# Overview
|
|
4
|
-
SDK used by client applications to interface with the Courier API.
|
|
5
|
-
 
|
|
6
|
-
<table>
|
|
7
|
-
<thead>
|
|
8
|
-
<tr>
|
|
9
|
-
<th width="880px" align="left">Requirements</th>
|
|
10
|
-
<th width="120px" align="center"></th>
|
|
11
|
-
</tr>
|
|
12
|
-
</thead>
|
|
13
|
-
<tbody>
|
|
14
|
-
<tr width="600px">
|
|
15
|
-
<td align="left">Courier Account</td>
|
|
16
|
-
<td align="center">
|
|
17
|
-
<a href="https://app.courier.com/signup">
|
|
18
|
-
<code>Sign Up</code>
|
|
19
|
-
</a>
|
|
20
|
-
</td>
|
|
21
|
-
</tr>
|
|
22
|
-
<tr width="600px">
|
|
23
|
-
<td align="left">Client API Key</td>
|
|
24
|
-
<td align="center">
|
|
25
|
-
<a href="https://app.courier.com/settings/api-keys">
|
|
26
|
-
<code>Get key</code>
|
|
27
|
-
</a>
|
|
28
|
-
</td>
|
|
29
|
-
</tr>
|
|
30
|
-
</tbody>
|
|
31
|
-
</table>
|
|
32
|
-
 
|
|
33
|
-
|
|
34
|
-
# Supported Interfaces
|
|
35
|
-
<table>
|
|
36
|
-
<thead>
|
|
37
|
-
<tr>
|
|
38
|
-
<th width="250px" align="left">Feature</th>
|
|
39
|
-
<th width="725px" align="left">Description</th>
|
|
40
|
-
</tr>
|
|
41
|
-
</thead>
|
|
42
|
-
<tbody>
|
|
43
|
-
<tr width="600px">
|
|
44
|
-
<td align="left">
|
|
45
|
-
<code>Identify</code>
|
|
46
|
-
</td>
|
|
47
|
-
<td align="left">
|
|
48
|
-
Event that triggers a user <a href="https://www.courier.com/docs/reference/profiles/create/"><code>Create</code></a> or <a href="https://www.courier.com/docs/reference/profiles/patch/"><code>Update</code></a> within Courier Profiles.
|
|
49
|
-
</td>
|
|
50
|
-
</tr>
|
|
51
|
-
<tr width="600px">
|
|
52
|
-
<td align="left">
|
|
53
|
-
<code>Track</code>
|
|
54
|
-
</td>
|
|
55
|
-
<td align="left">
|
|
56
|
-
Event ingested by Courier can be used to<a href="https://www.courier.com/docs/automations/designer/"> trigger an automation</a> or supply inline payloads within an <a href="https://app.courier.com/automations">existing</a> automation workflow.
|
|
57
|
-
</td>
|
|
58
|
-
</tr>
|
|
59
|
-
<tr width="600px">
|
|
60
|
-
<td align="left">
|
|
61
|
-
<code>GeneratePreferencesUrl</code>
|
|
62
|
-
</td>
|
|
63
|
-
<td align="left">
|
|
64
|
-
Generates URL that can be used to link users to <a href="https://www.courier.com/docs/courier-preferences/preference-center/introduction/">Preferences Center</a> to manage their notification preferences.
|
|
65
|
-
</td>
|
|
66
|
-
</tr>
|
|
67
|
-
</tbody>
|
|
68
|
-
</table>
|
|
69
|
-
|
|
70
|
-
# Installation
|
|
71
|
-
|
|
72
|
-
```sh
|
|
73
|
-
# npm
|
|
74
|
-
npm install @trycourier/courier-js
|
|
75
|
-
# yarn
|
|
76
|
-
yarn add @trycourier/courier-js
|
|
77
|
-
# pnpm
|
|
78
|
-
pnpm add @trycourier/courier-js
|
|
79
|
-
```
|
|
80
|
-
 
|
|
81
|
-
# Usage
|
|
82
|
-
|
|
83
|
-
## Initializing Client
|
|
84
|
-
```ts
|
|
85
|
-
import courier from "@trycourier/courier-js";
|
|
86
|
-
|
|
87
|
-
courier.init({
|
|
88
|
-
clientKey: "<REPLACE_WITH_YOUR_CLIENT_KEY>",
|
|
89
|
-
debug: true, // debug enables client side logs for error catching
|
|
90
|
-
});
|
|
91
|
-
```
|
|
92
|
-
## Identify
|
|
93
|
-
```ts
|
|
94
|
-
await courierSDK.identify("purbleUserId", {
|
|
95
|
-
email: "customer@purbleplace.com",
|
|
96
|
-
favoriteColor: "purple",
|
|
97
|
-
});
|
|
98
|
-
```
|
|
99
|
-
## Track
|
|
100
|
-
### Basic
|
|
101
|
-
```ts
|
|
102
|
-
await courierSDK.track("user-signup");
|
|
103
|
-
```
|
|
104
|
-
### With data payload
|
|
105
|
-
```ts
|
|
106
|
-
await courierSDK.track("bake-cake", {
|
|
107
|
-
cakeFlavor: "carrot",
|
|
108
|
-
frosting: "cream cheese"
|
|
109
|
-
});
|
|
110
|
-
```
|
|
111
|
-
## GeneratePreferencesUrl
|
|
112
|
-
```ts
|
|
113
|
-
const prefCenterLink = courier.generatePreferencesUrl("<user-id>", {
|
|
114
|
-
// optional
|
|
115
|
-
brandId: "<brand-id>",
|
|
116
|
-
});
|
|
117
|
-
```
|
|
118
|
-
# **Share feedback with Courier**
|
|
119
|
-
|
|
120
|
-
We are building the best SDKs for handling notifications! Have an idea or feedback about our SDKs? Here are some links to contact us:
|
|
121
|
-
|
|
122
|
-
- [Courier Feedback](https://feedback.courier.com/)
|
|
123
|
-
- [Courier JS Issues](https://github.com/trycourier/courier-js/issues)
|