@openemail/sdk 0.0.1

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.
Files changed (6) hide show
  1. package/README.md +183 -0
  2. package/index.cjs +1776 -0
  3. package/index.d.cts +1531 -0
  4. package/index.d.ts +1531 -0
  5. package/index.js +1744 -0
  6. package/package.json +60 -0
package/index.cjs ADDED
@@ -0,0 +1,1776 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var version = "0.0.1";
6
+
7
+ const BUILD_BASE_URL = 'https://api.openemail.uk';
8
+
9
+ const DEFAULTS = {
10
+ BASE_URL: BUILD_BASE_URL,
11
+ MAX_RETRIES: 2,
12
+ TIMEOUT_MS: 30_000,
13
+ USER_AGENT: `openemail-sdk/${version}`
14
+ };
15
+ const BROWSER_REFUSAL = [
16
+ 'OpenEmail refused to start in a browser.',
17
+ '',
18
+ ' This client carries a workspace API key that can send mail and read the mailbox. Anything',
19
+ ' you ship to a browser is readable by anyone who opens devtools.',
20
+ '',
21
+ ' Call it from a server, a serverless function or a script instead. Disposable inboxes are',
22
+ ' the exception: createTempMail() needs no API key and is safe to use in a browser.',
23
+ '',
24
+ ' If you are certain this environment is not a real browser, for example a test harness that',
25
+ ' defines window and document, pass { dangerouslyAllowBrowser: true }.'
26
+ ].join('\n');
27
+ const ENV_VARS = {
28
+ API_KEY: 'OPENEMAIL_API_KEY',
29
+ BASE_URL: 'OPENEMAIL_BASE_URL'
30
+ };
31
+ const API_KEY_PREFIXES = {
32
+ LIVE: 'oe_live_',
33
+ TEST: 'oe_test_'
34
+ };
35
+ const API_KEY_MODES = {
36
+ LIVE: 'live',
37
+ TEST: 'test'
38
+ };
39
+ const CLIENT_MESSAGES = {
40
+ API_KEY_REQUIRED: `An OpenEmail API key is required. Pass { apiKey } or set ${ENV_VARS.API_KEY}. Create one in OpenEmail under Settings, API keys.`,
41
+ API_KEY_SHAPE: 'is not an OpenEmail API key. The API accepts only keys beginning "oe_live_" or "oe_test_", so a session cookie, a session token or a key for another service is refused.',
42
+ BASE_URL_SHAPE: 'is not a usable base URL. Pass an origin such as "https://api.openemail.uk", or the http origin of a server you run yourself.',
43
+ NO_FETCH: 'No global fetch is available. Pass one as { fetch }, or run on Node 20+, Bun, Deno, Cloudflare Workers or a browser.',
44
+ EMPTY_SEGMENT: 'An id must not be empty.',
45
+ DOT_SEGMENT: 'is not a usable id: a path segment made only of dots is removed by URL parsers, so the request would reach a different endpoint.'
46
+ };
47
+
48
+ const MESSAGE_ENCRYPTION_FORMATS = [
49
+ 'pgp-mime',
50
+ 'pgp-signed',
51
+ 'pgp-inline',
52
+ 'smime-encrypted',
53
+ 'smime-signed'
54
+ ];
55
+ const SEALED_ENCRYPTION_FORMATS = ['pgp-mime', 'pgp-inline', 'smime-encrypted'];
56
+ const BASE64_CHUNK_SIZE = 0x8000;
57
+
58
+ const ERROR_TYPES = [
59
+ 'invalid_request_error',
60
+ 'authentication_error',
61
+ 'permission_error',
62
+ 'not_found_error',
63
+ 'conflict_error',
64
+ 'validation_error',
65
+ 'rate_limit_error',
66
+ 'api_error'
67
+ ];
68
+ const ERROR_TYPE = {
69
+ INVALID_REQUEST: 'invalid_request_error',
70
+ AUTHENTICATION: 'authentication_error',
71
+ PERMISSION: 'permission_error',
72
+ NOT_FOUND: 'not_found_error',
73
+ CONFLICT: 'conflict_error',
74
+ VALIDATION: 'validation_error',
75
+ RATE_LIMIT: 'rate_limit_error',
76
+ API: 'api_error'
77
+ };
78
+ const ERROR_TYPE_BY_STATUS = {
79
+ 401: ERROR_TYPE.AUTHENTICATION,
80
+ 403: ERROR_TYPE.PERMISSION,
81
+ 404: ERROR_TYPE.NOT_FOUND,
82
+ 409: ERROR_TYPE.CONFLICT,
83
+ 422: ERROR_TYPE.VALIDATION,
84
+ 429: ERROR_TYPE.RATE_LIMIT
85
+ };
86
+ const ERROR_CODES = {
87
+ INSUFFICIENT_SCOPE: 'insufficient_scope',
88
+ UNRECOGNISED_RESPONSE: 'unrecognised_response'
89
+ };
90
+ const ERROR_NAMES = {
91
+ API: 'OpenEmailApiError',
92
+ NETWORK: 'OpenEmailNetworkError',
93
+ TIMEOUT: 'TimeoutError'};
94
+
95
+ const HTTP_METHODS = {
96
+ GET: 'GET',
97
+ POST: 'POST',
98
+ PATCH: 'PATCH',
99
+ DELETE: 'DELETE'
100
+ };
101
+ const HEADER_KEYS = {
102
+ AUTHORIZATION: 'Authorization',
103
+ ACCEPT: 'Accept',
104
+ CONTENT_TYPE: 'Content-Type',
105
+ USER_AGENT: 'User-Agent',
106
+ IDEMPOTENCY_KEY: 'Idempotency-Key',
107
+ RETRY_AFTER: 'retry-after',
108
+ REQUEST_ID: 'x-request-id'
109
+ };
110
+ const CONTENT_TYPES = {
111
+ JSON: 'application/json',
112
+ CALENDAR: 'text/calendar'
113
+ };
114
+ const RETRY = {
115
+ STATUSES: [408, 429, 500, 502, 503, 504],
116
+ METHODS: ['GET'],
117
+ BASE_BACKOFF_MS: 500,
118
+ MAX_BACKOFF_MS: 8_000,
119
+ MAX_HONOURED_RETRY_AFTER_MS: 60_000,
120
+ RATE_LIMIT_STATUS: 429
121
+ };
122
+ const IDEMPOTENCY_KEY_PREFIX = 'oe-';
123
+
124
+ const LANGUAGES = [
125
+ { code: 'en', label: 'English', native: 'English', flag: '🇬🇧', rtl: false },
126
+ { code: 'es', label: 'Spanish', native: 'Español', flag: '🇪🇸', rtl: false },
127
+ { code: 'zh-Hans', label: 'Simplified Chinese', native: '简体中文', flag: '🇨🇳', rtl: false },
128
+ { code: 'zh-Hant', label: 'Traditional Chinese', native: '繁體中文', flag: '🇹🇼', rtl: false },
129
+ { code: 'hi', label: 'Hindi', native: 'हिन्दी', flag: '🇮🇳', rtl: false },
130
+ { code: 'ar', label: 'Arabic', native: 'العربية', flag: '🇸🇦', rtl: true },
131
+ { code: 'pt-BR', label: 'Brazilian Portuguese', native: 'Português (Brasil)', flag: '🇧🇷', rtl: false },
132
+ { code: 'pt', label: 'Portuguese', native: 'Português', flag: '🇵🇹', rtl: false },
133
+ { code: 'fr', label: 'French', native: 'Français', flag: '🇫🇷', rtl: false },
134
+ { code: 'de', label: 'German', native: 'Deutsch', flag: '🇩🇪', rtl: false },
135
+ { code: 'ru', label: 'Russian', native: 'Русский', flag: '🇷🇺', rtl: false },
136
+ { code: 'ja', label: 'Japanese', native: '日本語', flag: '🇯🇵', rtl: false },
137
+ { code: 'ko', label: 'Korean', native: '한국어', flag: '🇰🇷', rtl: false },
138
+ { code: 'it', label: 'Italian', native: 'Italiano', flag: '🇮🇹', rtl: false },
139
+ { code: 'nl', label: 'Dutch', native: 'Nederlands', flag: '🇳🇱', rtl: false },
140
+ { code: 'tr', label: 'Turkish', native: 'Türkçe', flag: '🇹🇷', rtl: false },
141
+ { code: 'pl', label: 'Polish', native: 'Polski', flag: '🇵🇱', rtl: false },
142
+ { code: 'uk', label: 'Ukrainian', native: 'Українська', flag: '🇺🇦', rtl: false },
143
+ { code: 'vi', label: 'Vietnamese', native: 'Tiếng Việt', flag: '🇻🇳', rtl: false },
144
+ { code: 'id', label: 'Indonesian', native: 'Bahasa Indonesia', flag: '🇮🇩', rtl: false },
145
+ { code: 'th', label: 'Thai', native: 'ไทย', flag: '🇹🇭', rtl: false },
146
+ { code: 'sv', label: 'Swedish', native: 'Svenska', flag: '🇸🇪', rtl: false },
147
+ { code: 'da', label: 'Danish', native: 'Dansk', flag: '🇩🇰', rtl: false },
148
+ { code: 'no', label: 'Norwegian', native: 'Norsk', flag: '🇳🇴', rtl: false },
149
+ { code: 'fi', label: 'Finnish', native: 'Suomi', flag: '🇫🇮', rtl: false },
150
+ { code: 'cs', label: 'Czech', native: 'Čeština', flag: '🇨🇿', rtl: false },
151
+ { code: 'el', label: 'Greek', native: 'Ελληνικά', flag: '🇬🇷', rtl: false },
152
+ { code: 'he', label: 'Hebrew', native: 'עברית', flag: '🇮🇱', rtl: true },
153
+ { code: 'ro', label: 'Romanian', native: 'Română', flag: '🇷🇴', rtl: false },
154
+ { code: 'hu', label: 'Hungarian', native: 'Magyar', flag: '🇭🇺', rtl: false },
155
+ { code: 'bn', label: 'Bengali', native: 'বাংলা', flag: '🇧🇩', rtl: false },
156
+ { code: 'ur', label: 'Urdu', native: 'اردو', flag: '🇵🇰', rtl: true },
157
+ { code: 'fa', label: 'Persian', native: 'فارسی', flag: '🇮🇷', rtl: true },
158
+ { code: 'ms', label: 'Malay', native: 'Bahasa Melayu', flag: '🇲🇾', rtl: false },
159
+ { code: 'ta', label: 'Tamil', native: 'தமிழ்', flag: '🇮🇳', rtl: false },
160
+ { code: 'ca', label: 'Catalan', native: 'Català', flag: '🇦🇩', rtl: false },
161
+ { code: 'en-GB', label: 'British English', native: 'English (United Kingdom)', flag: '🇬🇧', rtl: false },
162
+ { code: 'en-US', label: 'American English', native: 'English (United States)', flag: '🇺🇸', rtl: false },
163
+ { code: 'es-419', label: 'Latin American Spanish', native: 'Español (Latinoamérica)', flag: '🇲🇽', rtl: false },
164
+ { code: 'fr-CA', label: 'Canadian French', native: 'Français (Canada)', flag: '🇨🇦', rtl: false },
165
+ { code: 'de-AT', label: 'Austrian German', native: 'Deutsch (Österreich)', flag: '🇦🇹', rtl: false },
166
+ { code: 'de-CH', label: 'Swiss Standard German', native: 'Deutsch (Schweiz)', flag: '🇨🇭', rtl: false },
167
+ { code: 'nl-BE', label: 'Flemish', native: 'Vlaams', flag: '🇧🇪', rtl: false },
168
+ { code: 'zh-HK', label: 'Hong Kong Chinese', native: '繁體中文(香港)', flag: '🇭🇰', rtl: false },
169
+ { code: 'yue', label: 'Cantonese', native: '粵語', flag: '🇭🇰', rtl: false },
170
+ { code: 'nb', label: 'Norwegian Bokmål', native: 'Norsk bokmål', flag: '🇳🇴', rtl: false },
171
+ { code: 'nn', label: 'Norwegian Nynorsk', native: 'Norsk nynorsk', flag: '🇳🇴', rtl: false },
172
+ { code: 'sr', label: 'Serbian', native: 'Српски', flag: '🇷🇸', rtl: false },
173
+ { code: 'sr-Latn', label: 'Serbian (Latin)', native: 'Srpski (latinica)', flag: '🇷🇸', rtl: false },
174
+ { code: 'ar-EG', label: 'Egyptian Arabic', native: 'العربية (مصر)', flag: '🇪🇬', rtl: true },
175
+ { code: 'ar-MA', label: 'Moroccan Arabic', native: 'الدارجة المغربية', flag: '🇲🇦', rtl: true },
176
+ { code: 'sk', label: 'Slovak', native: 'Slovenčina', flag: '🇸🇰', rtl: false },
177
+ { code: 'sl', label: 'Slovenian', native: 'Slovenščina', flag: '🇸🇮', rtl: false },
178
+ { code: 'hr', label: 'Croatian', native: 'Hrvatski', flag: '🇭🇷', rtl: false },
179
+ { code: 'bs', label: 'Bosnian', native: 'Bosanski', flag: '🇧🇦', rtl: false },
180
+ { code: 'cnr', label: 'Montenegrin', native: 'Crnogorski', flag: '🇲🇪', rtl: false },
181
+ { code: 'mk', label: 'Macedonian', native: 'Македонски', flag: '🇲🇰', rtl: false },
182
+ { code: 'bg', label: 'Bulgarian', native: 'Български', flag: '🇧🇬', rtl: false },
183
+ { code: 'be', label: 'Belarusian', native: 'Беларуская', flag: '🇧🇾', rtl: false },
184
+ { code: 'hsb', label: 'Upper Sorbian', native: 'Hornjoserbšćina', flag: '🇩🇪', rtl: false },
185
+ { code: 'rue', label: 'Rusyn', native: 'Русиньскый', flag: '🇸🇰', rtl: false },
186
+ { code: 'lt', label: 'Lithuanian', native: 'Lietuvių', flag: '🇱🇹', rtl: false },
187
+ { code: 'lv', label: 'Latvian', native: 'Latviešu', flag: '🇱🇻', rtl: false },
188
+ { code: 'et', label: 'Estonian', native: 'Eesti', flag: '🇪🇪', rtl: false },
189
+ { code: 'se', label: 'Northern Sami', native: 'Davvisámegiella', flag: '🇳🇴', rtl: false },
190
+ { code: 'is', label: 'Icelandic', native: 'Íslenska', flag: '🇮🇸', rtl: false },
191
+ { code: 'fo', label: 'Faroese', native: 'Føroyskt', flag: '🇫🇴', rtl: false },
192
+ { code: 'kl', label: 'Greenlandic', native: 'Kalaallisut', flag: '🇬🇱', rtl: false },
193
+ { code: 'ga', label: 'Irish', native: 'Gaeilge', flag: '🇮🇪', rtl: false },
194
+ { code: 'gd', label: 'Scottish Gaelic', native: 'Gàidhlig', flag: '🇬🇧', rtl: false },
195
+ { code: 'cy', label: 'Welsh', native: 'Cymraeg', flag: '🇬🇧', rtl: false },
196
+ { code: 'kw', label: 'Cornish', native: 'Kernewek', flag: '🇬🇧', rtl: false },
197
+ { code: 'gv', label: 'Manx', native: 'Gaelg', flag: '🇮🇲', rtl: false },
198
+ { code: 'br', label: 'Breton', native: 'Brezhoneg', flag: '🇫🇷', rtl: false },
199
+ { code: 'eu', label: 'Basque', native: 'Euskara', flag: '🇪🇸', rtl: false },
200
+ { code: 'gl', label: 'Galician', native: 'Galego', flag: '🇪🇸', rtl: false },
201
+ { code: 'ast', label: 'Asturian', native: 'Asturianu', flag: '🇪🇸', rtl: false },
202
+ { code: 'an', label: 'Aragonese', native: 'Aragonés', flag: '🇪🇸', rtl: false },
203
+ { code: 'oc', label: 'Occitan', native: 'Occitan', flag: '🇫🇷', rtl: false },
204
+ { code: 'co', label: 'Corsican', native: 'Corsu', flag: '🇫🇷', rtl: false },
205
+ { code: 'sc', label: 'Sardinian', native: 'Sardu', flag: '🇮🇹', rtl: false },
206
+ { code: 'fur', label: 'Friulian', native: 'Furlan', flag: '🇮🇹', rtl: false },
207
+ { code: 'rm', label: 'Romansh', native: 'Rumantsch', flag: '🇨🇭', rtl: false },
208
+ { code: 'gsw', label: 'Alemannic German', native: 'Schwiizerdütsch', flag: '🇨🇭', rtl: false },
209
+ { code: 'lb', label: 'Luxembourgish', native: 'Lëtzebuergesch', flag: '🇱🇺', rtl: false },
210
+ { code: 'nds', label: 'Low German', native: 'Plattdüütsch', flag: '🇩🇪', rtl: false },
211
+ { code: 'bar', label: 'Bavarian', native: 'Boarisch', flag: '🇩🇪', rtl: false },
212
+ { code: 'fy', label: 'Western Frisian', native: 'Frysk', flag: '🇳🇱', rtl: false },
213
+ { code: 'li', label: 'Limburgish', native: 'Limburgs', flag: '🇳🇱', rtl: false },
214
+ { code: 'wa', label: 'Walloon', native: 'Walon', flag: '🇧🇪', rtl: false },
215
+ { code: 'mt', label: 'Maltese', native: 'Malti', flag: '🇲🇹', rtl: false },
216
+ { code: 'sq', label: 'Albanian', native: 'Shqip', flag: '🇦🇱', rtl: false },
217
+ { code: 'la', label: 'Latin', native: 'Latina', flag: '🇻🇦', rtl: false },
218
+ { code: 'eo', label: 'Esperanto', native: 'Esperanto', flag: '🇪🇺', rtl: false },
219
+ { code: 'yi', label: 'Yiddish', native: 'ייִדיש', flag: '🇮🇱', rtl: true },
220
+ { code: 'hy', label: 'Armenian', native: 'Հայերեն', flag: '🇦🇲', rtl: false },
221
+ { code: 'ka', label: 'Georgian', native: 'ქართული', flag: '🇬🇪', rtl: false },
222
+ { code: 'os', label: 'Ossetian', native: 'Ирон æвзаг', flag: '🇬🇪', rtl: false },
223
+ { code: 'ab', label: 'Abkhaz', native: 'Аԥсшәа', flag: '🇬🇪', rtl: false },
224
+ { code: 'ce', label: 'Chechen', native: 'Нохчийн мотт', flag: '🇷🇺', rtl: false },
225
+ { code: 'az', label: 'Azerbaijani', native: 'Azərbaycan dili', flag: '🇦🇿', rtl: false },
226
+ { code: 'kk', label: 'Kazakh', native: 'Қазақ тілі', flag: '🇰🇿', rtl: false },
227
+ { code: 'ky', label: 'Kyrgyz', native: 'Кыргызча', flag: '🇰🇬', rtl: false },
228
+ { code: 'uz', label: 'Uzbek', native: 'Oʻzbekcha', flag: '🇺🇿', rtl: false },
229
+ { code: 'tk', label: 'Turkmen', native: 'Türkmençe', flag: '🇹🇲', rtl: false },
230
+ { code: 'tt', label: 'Tatar', native: 'Татар теле', flag: '🇷🇺', rtl: false },
231
+ { code: 'ba', label: 'Bashkir', native: 'Башҡортса', flag: '🇷🇺', rtl: false },
232
+ { code: 'cv', label: 'Chuvash', native: 'Чӑвашла', flag: '🇷🇺', rtl: false },
233
+ { code: 'sah', label: 'Yakut', native: 'Саха тыла', flag: '🇷🇺', rtl: false },
234
+ { code: 'ug', label: 'Uyghur', native: 'ئۇيغۇرچە', flag: '🇨🇳', rtl: true },
235
+ { code: 'mn', label: 'Mongolian', native: 'Монгол хэл', flag: '🇲🇳', rtl: false },
236
+ { code: 'bo', label: 'Tibetan', native: 'བོད་སྐད་', flag: '🇨🇳', rtl: false },
237
+ { code: 'dz', label: 'Dzongkha', native: 'རྫོང་ཁ', flag: '🇧🇹', rtl: false },
238
+ { code: 'fa-AF', label: 'Dari', native: 'دری', flag: '🇦🇫', rtl: true },
239
+ { code: 'ps', label: 'Pashto', native: 'پښتو', flag: '🇦🇫', rtl: true },
240
+ { code: 'ku', label: 'Kurmanji Kurdish', native: 'Kurmancî', flag: '🇹🇷', rtl: false },
241
+ { code: 'ckb', label: 'Sorani Kurdish', native: 'کوردیی ناوەندی', flag: '🇮🇶', rtl: true },
242
+ { code: 'syr', label: 'Syriac', native: 'ܣܘܪܝܝܐ', flag: '🇮🇶', rtl: true },
243
+ { code: 'te', label: 'Telugu', native: 'తెలుగు', flag: '🇮🇳', rtl: false },
244
+ { code: 'mr', label: 'Marathi', native: 'मराठी', flag: '🇮🇳', rtl: false },
245
+ { code: 'gu', label: 'Gujarati', native: 'ગુજરાતી', flag: '🇮🇳', rtl: false },
246
+ { code: 'kn', label: 'Kannada', native: 'ಕನ್ನಡ', flag: '🇮🇳', rtl: false },
247
+ { code: 'ml', label: 'Malayalam', native: 'മലയാളം', flag: '🇮🇳', rtl: false },
248
+ { code: 'or', label: 'Odia', native: 'ଓଡ଼ିଆ', flag: '🇮🇳', rtl: false },
249
+ { code: 'pa', label: 'Punjabi', native: 'ਪੰਜਾਬੀ', flag: '🇮🇳', rtl: false },
250
+ { code: 'pa-Arab', label: 'Western Punjabi', native: 'پنجابی', flag: '🇵🇰', rtl: true },
251
+ { code: 'as', label: 'Assamese', native: 'অসমীয়া', flag: '🇮🇳', rtl: false },
252
+ { code: 'mai', label: 'Maithili', native: 'मैथिली', flag: '🇮🇳', rtl: false },
253
+ { code: 'bho', label: 'Bhojpuri', native: 'भोजपुरी', flag: '🇮🇳', rtl: false },
254
+ { code: 'sat', label: 'Santali', native: 'ᱥᱟᱱᱛᱟᱲᱤ', flag: '🇮🇳', rtl: false },
255
+ { code: 'ks', label: 'Kashmiri', native: 'کٲشُر', flag: '🇮🇳', rtl: true },
256
+ { code: 'sa', label: 'Sanskrit', native: 'संस्कृतम्', flag: '🇮🇳', rtl: false },
257
+ { code: 'ne', label: 'Nepali', native: 'नेपाली', flag: '🇳🇵', rtl: false },
258
+ { code: 'si', label: 'Sinhala', native: 'සිංහල', flag: '🇱🇰', rtl: false },
259
+ { code: 'sd', label: 'Sindhi', native: 'سنڌي', flag: '🇵🇰', rtl: true },
260
+ { code: 'dv', label: 'Divehi', native: 'ދިވެހި', flag: '🇲🇻', rtl: true },
261
+ { code: 'km', label: 'Khmer', native: 'ភាសាខ្មែរ', flag: '🇰🇭', rtl: false },
262
+ { code: 'lo', label: 'Lao', native: 'ລາວ', flag: '🇱🇦', rtl: false },
263
+ { code: 'my', label: 'Burmese', native: 'မြန်မာဘာသာ', flag: '🇲🇲', rtl: false },
264
+ { code: 'jv', label: 'Javanese', native: 'Basa Jawa', flag: '🇮🇩', rtl: false },
265
+ { code: 'su', label: 'Sundanese', native: 'Basa Sunda', flag: '🇮🇩', rtl: false },
266
+ { code: 'fil', label: 'Filipino', native: 'Filipino', flag: '🇵🇭', rtl: false },
267
+ { code: 'tl', label: 'Tagalog', native: 'Tagalog', flag: '🇵🇭', rtl: false },
268
+ { code: 'ceb', label: 'Cebuano', native: 'Binisaya', flag: '🇵🇭', rtl: false },
269
+ { code: 'ilo', label: 'Ilocano', native: 'Ilokano', flag: '🇵🇭', rtl: false },
270
+ { code: 'war', label: 'Waray', native: 'Winaray', flag: '🇵🇭', rtl: false },
271
+ { code: 'sw', label: 'Swahili', native: 'Kiswahili', flag: '🇰🇪', rtl: false },
272
+ { code: 'am', label: 'Amharic', native: 'አማርኛ', flag: '🇪🇹', rtl: false },
273
+ { code: 'ti', label: 'Tigrinya', native: 'ትግርኛ', flag: '🇪🇷', rtl: false },
274
+ { code: 'om', label: 'Oromo', native: 'Afaan Oromoo', flag: '🇪🇹', rtl: false },
275
+ { code: 'so', label: 'Somali', native: 'Soomaali', flag: '🇸🇴', rtl: false },
276
+ { code: 'ha', label: 'Hausa', native: 'Hausa', flag: '🇳🇬', rtl: false },
277
+ { code: 'yo', label: 'Yoruba', native: 'Yorùbá', flag: '🇳🇬', rtl: false },
278
+ { code: 'ig', label: 'Igbo', native: 'Igbo', flag: '🇳🇬', rtl: false },
279
+ { code: 'af', label: 'Afrikaans', native: 'Afrikaans', flag: '🇿🇦', rtl: false },
280
+ { code: 'zu', label: 'Zulu', native: 'isiZulu', flag: '🇿🇦', rtl: false },
281
+ { code: 'xh', label: 'Xhosa', native: 'isiXhosa', flag: '🇿🇦', rtl: false },
282
+ { code: 'st', label: 'Sesotho', native: 'Sesotho', flag: '🇱🇸', rtl: false },
283
+ { code: 'nso', label: 'Sepedi', native: 'Sepedi', flag: '🇿🇦', rtl: false },
284
+ { code: 'tn', label: 'Setswana', native: 'Setswana', flag: '🇧🇼', rtl: false },
285
+ { code: 'ts', label: 'Xitsonga', native: 'Xitsonga', flag: '🇿🇦', rtl: false },
286
+ { code: 've', label: 'Tshivenda', native: 'Tshivenḓa', flag: '🇿🇦', rtl: false },
287
+ { code: 'ss', label: 'Swati', native: 'siSwati', flag: '🇸🇿', rtl: false },
288
+ { code: 'nr', label: 'Southern Ndebele', native: 'isiNdebele', flag: '🇿🇦', rtl: false },
289
+ { code: 'sn', label: 'Shona', native: 'chiShona', flag: '🇿🇼', rtl: false },
290
+ { code: 'ny', label: 'Chichewa', native: 'Chichewa', flag: '🇲🇼', rtl: false },
291
+ { code: 'rw', label: 'Kinyarwanda', native: 'Ikinyarwanda', flag: '🇷🇼', rtl: false },
292
+ { code: 'rn', label: 'Kirundi', native: 'Ikirundi', flag: '🇧🇮', rtl: false },
293
+ { code: 'lg', label: 'Luganda', native: 'Luganda', flag: '🇺🇬', rtl: false },
294
+ { code: 'ln', label: 'Lingala', native: 'Lingála', flag: '🇨🇩', rtl: false },
295
+ { code: 'kg', label: 'Kikongo', native: 'Kikongo', flag: '🇨🇩', rtl: false },
296
+ { code: 'wo', label: 'Wolof', native: 'Wolof', flag: '🇸🇳', rtl: false },
297
+ { code: 'ff', label: 'Fula', native: 'Pulaar', flag: '🇸🇳', rtl: false },
298
+ { code: 'bm', label: 'Bambara', native: 'Bamanankan', flag: '🇲🇱', rtl: false },
299
+ { code: 'ak', label: 'Akan', native: 'Akan', flag: '🇬🇭', rtl: false },
300
+ { code: 'tw', label: 'Twi', native: 'Twi', flag: '🇬🇭', rtl: false },
301
+ { code: 'ee', label: 'Ewe', native: 'Eʋegbe', flag: '🇬🇭', rtl: false },
302
+ { code: 'mg', label: 'Malagasy', native: 'Malagasy', flag: '🇲🇬', rtl: false },
303
+ { code: 'kab', label: 'Kabyle', native: 'Taqbaylit', flag: '🇩🇿', rtl: false },
304
+ { code: 'tzm', label: 'Tamazight', native: 'ⵜⴰⵎⴰⵣⵉⵖⵜ', flag: '🇲🇦', rtl: false },
305
+ { code: 'sg', label: 'Sango', native: 'Sängö', flag: '🇨🇫', rtl: false },
306
+ { code: 'ht', label: 'Haitian Creole', native: 'Kreyòl ayisyen', flag: '🇭🇹', rtl: false },
307
+ { code: 'pap', label: 'Papiamento', native: 'Papiamentu', flag: '🇦🇼', rtl: false },
308
+ { code: 'srn', label: 'Sranan Tongo', native: 'Sranantongo', flag: '🇸🇷', rtl: false },
309
+ { code: 'qu', label: 'Quechua', native: 'Runa Simi', flag: '🇵🇪', rtl: false },
310
+ { code: 'gn', label: 'Guarani', native: 'Avañe\'ẽ', flag: '🇵🇾', rtl: false },
311
+ { code: 'ay', label: 'Aymara', native: 'Aymar aru', flag: '🇧🇴', rtl: false },
312
+ { code: 'nah', label: 'Nahuatl', native: 'Nāhuatl', flag: '🇲🇽', rtl: false },
313
+ { code: 'chr', label: 'Cherokee', native: 'ᏣᎳᎩ', flag: '🇺🇸', rtl: false },
314
+ { code: 'nv', label: 'Navajo', native: 'Diné bizaad', flag: '🇺🇸', rtl: false },
315
+ { code: 'iu', label: 'Inuktitut', native: 'ᐃᓄᒃᑎᑐᑦ', flag: '🇨🇦', rtl: false },
316
+ { code: 'mi', label: 'Maori', native: 'Te reo Māori', flag: '🇳🇿', rtl: false },
317
+ { code: 'sm', label: 'Samoan', native: 'Gagana Sāmoa', flag: '🇼🇸', rtl: false },
318
+ { code: 'to', label: 'Tongan', native: 'Lea faka-Tonga', flag: '🇹🇴', rtl: false },
319
+ { code: 'fj', label: 'Fijian', native: 'Na Vosa Vakaviti', flag: '🇫🇯', rtl: false },
320
+ { code: 'haw', label: 'Hawaiian', native: 'ʻŌlelo Hawaiʻi', flag: '🇺🇸', rtl: false },
321
+ { code: 'ty', label: 'Tahitian', native: 'Reo Tahiti', flag: '🇵🇫', rtl: false },
322
+ { code: 'ch', label: 'Chamorro', native: 'Chamoru', flag: '🇬🇺', rtl: false },
323
+ { code: 'mh', label: 'Marshallese', native: 'Kajin M̧ajeļ', flag: '🇲🇭', rtl: false },
324
+ { code: 'tpi', label: 'Tok Pisin', native: 'Tok Pisin', flag: '🇵🇬', rtl: false }
325
+ ];
326
+ const LANGUAGE_ALIASES = [
327
+ ['zh', 'zh-Hans'],
328
+ ['zh-cn', 'zh-Hans'],
329
+ ['zh-sg', 'zh-Hans'],
330
+ ['cmn', 'zh-Hans'],
331
+ ['zh-tw', 'zh-Hant'],
332
+ ['zh-mo', 'zh-HK'],
333
+ ['iw', 'he'],
334
+ ['in', 'id'],
335
+ ['ji', 'yi'],
336
+ ['mo', 'ro'],
337
+ ['sh', 'sr-Latn'],
338
+ ['sr-latn-rs', 'sr-Latn'],
339
+ ['prs', 'fa-AF'],
340
+ ['fa-af', 'fa-AF'],
341
+ ['kmr', 'ku'],
342
+ ['pt-pt', 'pt'],
343
+ ['es-mx', 'es-419'],
344
+ ['es-ar', 'es-419'],
345
+ ['es-co', 'es-419'],
346
+ ['es-cl', 'es-419']
347
+ ];
348
+ const LANGUAGE_SUBTAG_PATTERN = /[-_]/;
349
+
350
+ const PAGINATION = {
351
+ MAX_LIMIT: 100,
352
+ DEFAULT_LIMIT: 25
353
+ };
354
+ const CURSOR_STYLES = {
355
+ CURSOR: { PARAM: 'cursor', FIELD: 'nextCursor' },
356
+ PAGE_TOKEN: { PARAM: 'pageToken', FIELD: 'nextPageToken' }
357
+ };
358
+ const QUERY_KEYS = {
359
+ LIMIT: 'limit',
360
+ STATUS: 'status',
361
+ FROM: 'from',
362
+ TO: 'to',
363
+ TIMEZONE: 'timezone',
364
+ ENABLED: 'enabled',
365
+ RULE_ID: 'ruleId',
366
+ THREAD_ID: 'threadId',
367
+ FOLDER: 'folder',
368
+ QUERY: 'query',
369
+ LABEL_IDS: 'labelIds',
370
+ OPENED: 'opened',
371
+ CLICKED: 'clicked',
372
+ DAYS: 'days',
373
+ MINUTES: 'minutes',
374
+ GRAIN: 'grain',
375
+ OFFSET_MINUTES: 'offsetMinutes',
376
+ INCLUDE_MACHINE: 'includeMachine',
377
+ REASSIGN_TO: 'reassignTo'
378
+ };
379
+ const LIST_SEPARATOR = ',';
380
+
381
+ const PATHS = {
382
+ PING: '/ping',
383
+ KEYS_SELF: '/keys/self',
384
+ ADDRESSES: '/addresses',
385
+ LANGUAGES: '/languages',
386
+ EMAILS: '/emails',
387
+ EMAILS_BATCH: '/emails/batch',
388
+ EMAILS_TRANSLATE: '/emails/translate',
389
+ THREADS: '/threads',
390
+ DRAFTS: '/drafts',
391
+ LABELS: '/labels',
392
+ CONTACTS: '/contacts',
393
+ DOMAINS: '/domains',
394
+ WEBHOOKS: '/webhooks',
395
+ TEMPLATES: '/templates',
396
+ RULES: '/rules',
397
+ RULES_RUNS: '/rules/runs',
398
+ RULES_REORDER: '/rules/reorder',
399
+ TRACKING: '/tracking',
400
+ TRACKING_STATS: '/tracking/stats',
401
+ CALENDAR_EVENTS: '/calendar/events',
402
+ SETTINGS: '/settings',
403
+ ROLES: '/roles',
404
+ ROLES_PERMISSIONS: '/roles/permissions',
405
+ MEMBERS: '/members',
406
+ TEMP_MAIL_DOMAINS: '/temp-mail/domains',
407
+ TEMP_MAIL_INBOXES: '/temp-mail/inboxes'
408
+ };
409
+ const SEGMENTS = {
410
+ ADDRESSES: 'addresses',
411
+ ATTACHMENTS: 'attachments',
412
+ CANCEL: 'cancel',
413
+ CLICKS: 'clicks',
414
+ DELIVERIES: 'deliveries',
415
+ EVENTS: 'events',
416
+ EXTEND: 'extend',
417
+ ICS: 'ics',
418
+ MESSAGES: 'messages',
419
+ OPENS: 'opens',
420
+ PREVIEW: 'preview',
421
+ ROTATE_SECRET: 'rotate-secret',
422
+ SEND: 'send',
423
+ SNOOZE: 'snooze',
424
+ TEST: 'test',
425
+ TRACKING: 'tracking',
426
+ TRASH: 'trash',
427
+ UNSNOOZE: 'unsnooze',
428
+ VERSIONS: 'versions'
429
+ };
430
+
431
+ const VERSION = version;
432
+ const NPM_REGISTRY_URL = 'https://registry.npmjs.org/@openemail/sdk';
433
+ const UPDATE_NOTICE = {
434
+ ENV_DISABLE: 'OPENEMAIL_DISABLE_UPDATE_NOTICE',
435
+ TIMEOUT_MS: 2_000,
436
+ PACKAGE: '@openemail/sdk',
437
+ CHANGELOG_URL: 'https://www.npmjs.com/package/@openemail/sdk?activeTab=versions',
438
+ ICON: 'ℹ'
439
+ };
440
+
441
+ const RULE_FIELDS = [
442
+ 'from',
443
+ 'from_domain',
444
+ 'envelope_from',
445
+ 'to',
446
+ 'cc',
447
+ 'bcc',
448
+ 'recipient',
449
+ 'reply_to',
450
+ 'delivered_to',
451
+ 'subject',
452
+ 'body',
453
+ 'header',
454
+ 'list_id',
455
+ 'attachment_name',
456
+ 'attachment_type',
457
+ 'has_attachment',
458
+ 'attachment_size',
459
+ 'message_size',
460
+ 'spam',
461
+ 'hour',
462
+ 'weekday'
463
+ ];
464
+ const RULE_OPERATORS = ['matches', 'contains', 'equals', 'starts_with', 'ends_with', 'gt', 'lt'];
465
+ const RULE_ACTIONS = [
466
+ 'label',
467
+ 'remove_label',
468
+ 'archive',
469
+ 'mark_read',
470
+ 'star',
471
+ 'spam',
472
+ 'trash',
473
+ 'forward',
474
+ 'reply',
475
+ 'block_sender',
476
+ 'reject'
477
+ ];
478
+
479
+ const API_SCOPES = [
480
+ 'emails:send',
481
+ 'emails:read',
482
+ 'drafts:read',
483
+ 'drafts:write',
484
+ 'threads:read',
485
+ 'threads:write',
486
+ 'labels:read',
487
+ 'labels:write',
488
+ 'contacts:read',
489
+ 'calendar:read',
490
+ 'calendar:write',
491
+ 'templates:read',
492
+ 'templates:write',
493
+ 'domains:read',
494
+ 'domains:write',
495
+ 'webhooks:read',
496
+ 'webhooks:write',
497
+ 'rules:read',
498
+ 'rules:write',
499
+ 'connections:read',
500
+ 'members:read',
501
+ 'members:write',
502
+ 'roles:read',
503
+ 'roles:write',
504
+ 'settings:read',
505
+ 'settings:write'
506
+ ];
507
+
508
+ const WEBHOOK_EVENTS = [
509
+ 'email.received',
510
+ 'email.sent',
511
+ 'email.failed',
512
+ 'email.cancelled',
513
+ 'email.scheduled',
514
+ 'email.queued',
515
+ 'email.bounced',
516
+ 'email.complained',
517
+ 'email.opened',
518
+ 'email.clicked'
519
+ ];
520
+ const WEBHOOK_HEADERS = {
521
+ SIGNATURE: 'X-OpenEmail-Signature',
522
+ EVENT: 'X-OpenEmail-Event',
523
+ DELIVERY: 'X-OpenEmail-Delivery'
524
+ };
525
+ const WEBHOOK_SIGNATURE = {
526
+ VERSION: 'v1',
527
+ TIMESTAMP: 't',
528
+ PAIR_SEPARATOR: ',',
529
+ VALUE_SEPARATOR: '=',
530
+ TOLERANCE_SECONDS: 300
531
+ };
532
+ const WEBHOOK_MESSAGES = {
533
+ MISSING_HEADER: 'Missing the X-OpenEmail-Signature header.',
534
+ MALFORMED_HEADER: 'X-OpenEmail-Signature is not in the form t=<seconds>,v1=<hex>.',
535
+ OUT_OF_TOLERANCE: 'Webhook timestamp is {{drift}}s out of tolerance ({{tolerance}}s).',
536
+ MISMATCH: 'Webhook signature does not match the payload.',
537
+ NO_WEBCRYPTO: 'No WebCrypto is available. Verifying a webhook signature needs globalThis.crypto.subtle, which Node 20+, Bun, Deno, Cloudflare Workers and browsers over HTTPS all provide.'
538
+ };
539
+
540
+ const PAGE_LIMITS = PAGINATION;
541
+ const WEBHOOK_SIGNATURE_HEADERS = WEBHOOK_HEADERS;
542
+
543
+ const CLIENT_REGISTRY_KEY = Symbol.for('openemail.sdk.client.v1');
544
+ const CLIENT_REGISTRY = globalThis;
545
+
546
+ const LOCAL_HOSTS = ['localhost', '127.0.0.1', '0.0.0.0', '::1'];
547
+ const ENDPOINT_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:\/\//i;
548
+
549
+ const RETRYABLE_STATUSES = new Set(RETRY.STATUSES);
550
+ const RETRYABLE_METHODS = new Set(RETRY.METHODS);
551
+
552
+ const IsApiKey = (value) => typeof value === 'string' && (value.startsWith(API_KEY_PREFIXES.LIVE) || value.startsWith(API_KEY_PREFIXES.TEST));
553
+ const ModeOf = (apiKey) => apiKey.startsWith(API_KEY_PREFIXES.TEST) ? API_KEY_MODES.TEST : API_KEY_MODES.LIVE;
554
+ const AssertApiKey = (apiKey, where) => {
555
+ if (!apiKey)
556
+ throw new Error(CLIENT_MESSAGES.API_KEY_REQUIRED);
557
+ if (!IsApiKey(apiKey))
558
+ throw new Error(`${where} ${CLIENT_MESSAGES.API_KEY_SHAPE}`);
559
+ };
560
+
561
+ const IsBrowser = () => {
562
+ const scope = globalThis;
563
+ if (scope.Deno !== undefined)
564
+ return false;
565
+ return typeof scope.window !== 'undefined' && typeof scope.document !== 'undefined';
566
+ };
567
+
568
+ class OpenEmailApiError extends Error {
569
+ status;
570
+ type;
571
+ code;
572
+ param;
573
+ docUrl;
574
+ requestId;
575
+ retryAfterSeconds;
576
+ constructor(props) {
577
+ const { message, status, type, code, param, docUrl, requestId, retryAfterSeconds } = props;
578
+ super(message);
579
+ this.name = ERROR_NAMES.API;
580
+ this.status = status;
581
+ this.type = type;
582
+ this.code = code;
583
+ this.param = param;
584
+ this.docUrl = docUrl;
585
+ this.requestId = requestId;
586
+ this.retryAfterSeconds = retryAfterSeconds;
587
+ }
588
+ get isAuth() {
589
+ return this.type === ERROR_TYPE.AUTHENTICATION;
590
+ }
591
+ get isPermission() {
592
+ return this.type === ERROR_TYPE.PERMISSION;
593
+ }
594
+ get isScopeMissing() {
595
+ return this.code === ERROR_CODES.INSUFFICIENT_SCOPE;
596
+ }
597
+ get isInvalidRequest() {
598
+ return this.type === ERROR_TYPE.INVALID_REQUEST;
599
+ }
600
+ get isValidation() {
601
+ return this.type === ERROR_TYPE.VALIDATION;
602
+ }
603
+ get isNotFound() {
604
+ return this.type === ERROR_TYPE.NOT_FOUND;
605
+ }
606
+ get isConflict() {
607
+ return this.type === ERROR_TYPE.CONFLICT;
608
+ }
609
+ get isRateLimited() {
610
+ return this.type === ERROR_TYPE.RATE_LIMIT;
611
+ }
612
+ get isServerError() {
613
+ return this.status !== null && this.status >= 500;
614
+ }
615
+ get isRetryable() {
616
+ return this.status !== null && RETRY.STATUSES.includes(this.status);
617
+ }
618
+ }
619
+ class OpenEmailNetworkError extends Error {
620
+ cause;
621
+ isTimeout;
622
+ constructor(message, cause, isTimeout = false) {
623
+ super(message);
624
+ this.name = ERROR_NAMES.NETWORK;
625
+ this.cause = cause;
626
+ this.isTimeout = isTimeout;
627
+ }
628
+ }
629
+
630
+ let checked = false;
631
+ const Parts = (value) => value
632
+ .trim()
633
+ .replace(/^v/, '')
634
+ .split('.')
635
+ .map(part => Number.parseInt(part, 10));
636
+ const IsNewer = (candidate, current) => {
637
+ const left = Parts(candidate);
638
+ const right = Parts(current);
639
+ if (left.length !== 3 || right.length !== 3)
640
+ return false;
641
+ if (left.some(Number.isNaN) || right.some(Number.isNaN))
642
+ return false;
643
+ for (let index = 0; index < 3; index++) {
644
+ if (left[index] > right[index])
645
+ return true;
646
+ if (left[index] < right[index])
647
+ return false;
648
+ }
649
+ return false;
650
+ };
651
+ const Disabled = () => {
652
+ if (typeof process === 'undefined')
653
+ return true;
654
+ const value = process.env?.[UPDATE_NOTICE.ENV_DISABLE];
655
+ if (value && value.length > 0)
656
+ return true;
657
+ return process.stdout?.isTTY !== true;
658
+ };
659
+ const LatestVersion = async (fetchImpl = globalThis.fetch) => {
660
+ const controller = new AbortController();
661
+ const timer = setTimeout(() => controller.abort(), UPDATE_NOTICE.TIMEOUT_MS);
662
+ timer.unref?.();
663
+ try {
664
+ const response = await fetchImpl(NPM_REGISTRY_URL, { signal: controller.signal });
665
+ if (!response.ok)
666
+ return null;
667
+ const payload = await response.json();
668
+ const latest = payload?.['dist-tags']?.latest;
669
+ return typeof latest === 'string' ? latest : null;
670
+ }
671
+ catch {
672
+ return null;
673
+ }
674
+ finally {
675
+ clearTimeout(timer);
676
+ }
677
+ };
678
+ const UpdateNotice = (fetchImpl) => {
679
+ if (checked || Disabled())
680
+ return;
681
+ checked = true;
682
+ void LatestVersion(fetchImpl)
683
+ .then(latest => {
684
+ if (!latest || !IsNewer(latest, VERSION))
685
+ return;
686
+ console.warn(`${UPDATE_NOTICE.ICON} ${UPDATE_NOTICE.PACKAGE} ${latest} is available, you are on ${VERSION}. ${UPDATE_NOTICE.CHANGELOG_URL}.`);
687
+ })
688
+ .catch(() => undefined);
689
+ };
690
+
691
+ const Backoff = (attempt) => {
692
+ const ceiling = Math.min(RETRY.MAX_BACKOFF_MS, RETRY.BASE_BACKOFF_MS * 2 ** attempt);
693
+ return Math.round(ceiling * (0.5 + Math.random() * 0.5));
694
+ };
695
+
696
+ const BuildUrl = (baseUrl, path, query) => {
697
+ const url = `${baseUrl.replace(/\/+$/, '')}${path}`;
698
+ if (!query)
699
+ return url;
700
+ const params = new URLSearchParams();
701
+ for (const [key, value] of Object.entries(query)) {
702
+ if (value === undefined || value === null || value === '')
703
+ continue;
704
+ params.append(key, String(value));
705
+ }
706
+ const serialized = params.toString();
707
+ return serialized ? `${url}?${serialized}` : url;
708
+ };
709
+
710
+ const Deadline = (timeoutMs, signal) => {
711
+ const controller = new AbortController();
712
+ let expired = false;
713
+ const timer = timeoutMs > 0
714
+ ? setTimeout(() => {
715
+ const reason = new Error(`Request timed out after ${timeoutMs}ms.`);
716
+ reason.name = ERROR_NAMES.TIMEOUT;
717
+ expired = true;
718
+ controller.abort(reason);
719
+ }, timeoutMs)
720
+ : undefined;
721
+ const OnAbort = () => controller.abort(signal?.reason);
722
+ if (signal?.aborted)
723
+ controller.abort(signal.reason);
724
+ else
725
+ signal?.addEventListener('abort', OnAbort, { once: true });
726
+ return {
727
+ signal: controller.signal,
728
+ timedOut: () => expired,
729
+ release: () => {
730
+ if (timer !== undefined)
731
+ clearTimeout(timer);
732
+ signal?.removeEventListener('abort', OnAbort);
733
+ }
734
+ };
735
+ };
736
+
737
+ const IdempotencyKey = () => {
738
+ const source = globalThis.crypto;
739
+ if (typeof source?.randomUUID === 'function')
740
+ return `${IDEMPOTENCY_KEY_PREFIX}${source.randomUUID()}`;
741
+ if (typeof source?.getRandomValues === 'function') {
742
+ const bytes = source.getRandomValues(new Uint8Array(16));
743
+ return `${IDEMPOTENCY_KEY_PREFIX}${Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('')}`;
744
+ }
745
+ return `${IDEMPOTENCY_KEY_PREFIX}${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 14)}`;
746
+ };
747
+
748
+ const RetryAfter = (value) => {
749
+ if (!value)
750
+ return undefined;
751
+ const seconds = Number(value);
752
+ if (Number.isFinite(seconds) && seconds >= 0)
753
+ return seconds;
754
+ const at = Date.parse(value);
755
+ if (!Number.isNaN(at))
756
+ return Math.max(0, Math.ceil((at - Date.now()) / 1000));
757
+ return undefined;
758
+ };
759
+
760
+ const Sleep = (ms, signal) => new Promise((resolve, reject) => {
761
+ if (signal?.aborted) {
762
+ reject(new OpenEmailNetworkError('Request aborted by the caller.', signal.reason));
763
+ return;
764
+ }
765
+ const onAbort = () => {
766
+ clearTimeout(timer);
767
+ reject(new OpenEmailNetworkError('Request aborted by the caller.', signal?.reason));
768
+ };
769
+ const timer = setTimeout(() => {
770
+ signal?.removeEventListener('abort', onAbort);
771
+ resolve();
772
+ }, ms);
773
+ signal?.addEventListener('abort', onAbort, { once: true });
774
+ });
775
+
776
+ const IsErrorType = (value) => typeof value === 'string' && ERROR_TYPES.includes(value);
777
+ const TypeForStatus = (status) => {
778
+ if (status >= 500)
779
+ return ERROR_TYPE.API;
780
+ const mapped = ERROR_TYPE_BY_STATUS[status];
781
+ return IsErrorType(mapped) ? mapped : ERROR_TYPE.INVALID_REQUEST;
782
+ };
783
+ const ReadPayload = (body) => {
784
+ if (typeof body !== 'object' || body === null || !('error' in body))
785
+ return {};
786
+ const error = body.error;
787
+ return typeof error === 'object' && error !== null ? error : {};
788
+ };
789
+ const Text = (value) => typeof value === 'string' && value.length > 0 ? value : undefined;
790
+ const ToErrorProps = (response, body, rawText, retryAfterSeconds) => {
791
+ const payload = ReadPayload(body);
792
+ return {
793
+ message: Text(payload.message)
794
+ ?? (rawText ? rawText.slice(0, 300) : `The API responded ${response.status} with a body this client did not recognise.`),
795
+ status: response.status,
796
+ type: IsErrorType(payload.type) ? payload.type : TypeForStatus(response.status),
797
+ code: Text(payload.code) ?? ERROR_CODES.UNRECOGNISED_RESPONSE,
798
+ param: Text(payload.param),
799
+ docUrl: Text(payload.docUrl),
800
+ requestId: Text(payload.requestId) ?? response.headers.get(HEADER_KEYS.REQUEST_ID) ?? undefined,
801
+ retryAfterSeconds
802
+ };
803
+ };
804
+
805
+ const Describe = (error) => error instanceof Error ? error.message : String(error);
806
+ const ParseJson = (text) => {
807
+ if (!text)
808
+ return undefined;
809
+ try {
810
+ return JSON.parse(text);
811
+ }
812
+ catch {
813
+ return undefined;
814
+ }
815
+ };
816
+ const ValidBaseUrl = (value) => {
817
+ const trimmed = value.trim().replace(/\/+$/, '');
818
+ try {
819
+ const parsed = new URL(trimmed);
820
+ if (parsed.protocol === 'https:' || parsed.protocol === 'http:')
821
+ return trimmed;
822
+ }
823
+ catch { }
824
+ throw new Error(`${JSON.stringify(value)} ${CLIENT_MESSAGES.BASE_URL_SHAPE}`);
825
+ };
826
+ class Transport {
827
+ #apiKey;
828
+ #inboxToken;
829
+ baseUrl;
830
+ fetchImpl;
831
+ maxRetries;
832
+ timeoutMs;
833
+ userAgent;
834
+ headers;
835
+ constructor(options) {
836
+ if (options.apiKey !== undefined)
837
+ AssertApiKey(options.apiKey, '{ apiKey }');
838
+ const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
839
+ if (!fetchImpl)
840
+ throw new Error(CLIENT_MESSAGES.NO_FETCH);
841
+ this.#apiKey = options.apiKey;
842
+ this.#inboxToken = options.inboxToken;
843
+ this.baseUrl = ValidBaseUrl(options.baseUrl ?? DEFAULTS.BASE_URL);
844
+ this.fetchImpl = fetchImpl;
845
+ this.maxRetries = Math.max(0, options.maxRetries ?? DEFAULTS.MAX_RETRIES);
846
+ this.timeoutMs = options.timeoutMs ?? DEFAULTS.TIMEOUT_MS;
847
+ this.userAgent = options.userAgent ?? DEFAULTS.USER_AGENT;
848
+ this.headers = { ...options.headers };
849
+ if (options.disableUpdateNotice !== true)
850
+ UpdateNotice(this.fetchImpl);
851
+ }
852
+ async request(path, options = {}) {
853
+ const method = (options.method ?? HTTP_METHODS.GET).toUpperCase();
854
+ const url = BuildUrl(this.baseUrl, path, options.query);
855
+ const headers = this.headersFor(options);
856
+ const body = options.body === undefined ? undefined : JSON.stringify(options.body);
857
+ const repeatable = options.repeatable ?? RETRYABLE_METHODS.has(method);
858
+ let attempt = 0;
859
+ for (;;) {
860
+ const outcome = await this.attempt(url, method, headers, body, options.signal);
861
+ if (outcome instanceof OpenEmailNetworkError) {
862
+ if (options.signal?.aborted || !repeatable || attempt >= this.maxRetries)
863
+ throw outcome;
864
+ await Sleep(Backoff(attempt), options.signal);
865
+ attempt += 1;
866
+ continue;
867
+ }
868
+ const { response, text } = outcome;
869
+ if (response.ok)
870
+ return this.parse(response, text, options.accept);
871
+ const retryAfter = RetryAfter(response.headers.get(HEADER_KEYS.RETRY_AFTER));
872
+ const honoured = retryAfter === undefined
873
+ ? response.status !== RETRY.RATE_LIMIT_STATUS
874
+ : retryAfter * 1000 <= RETRY.MAX_HONOURED_RETRY_AFTER_MS;
875
+ if (repeatable && honoured && attempt < this.maxRetries && RETRYABLE_STATUSES.has(response.status)) {
876
+ await Sleep(retryAfter === undefined ? Backoff(attempt) : retryAfter * 1000, options.signal);
877
+ attempt += 1;
878
+ continue;
879
+ }
880
+ throw new OpenEmailApiError(ToErrorProps(response, ParseJson(text), text, retryAfter));
881
+ }
882
+ }
883
+ headersFor(options) {
884
+ const headers = {
885
+ ...this.headers,
886
+ [HEADER_KEYS.ACCEPT]: options.accept ?? CONTENT_TYPES.JSON
887
+ };
888
+ if (!IsBrowser())
889
+ headers[HEADER_KEYS.USER_AGENT] = this.userAgent;
890
+ if (options.body !== undefined)
891
+ headers[HEADER_KEYS.CONTENT_TYPE] = CONTENT_TYPES.JSON;
892
+ if (options.anonymous !== true) {
893
+ if (options.apiKey !== undefined)
894
+ AssertApiKey(options.apiKey, '{ apiKey } on this call');
895
+ const credential = options.inboxToken ?? options.apiKey ?? this.#inboxToken ?? this.#apiKey;
896
+ if (credential)
897
+ headers[HEADER_KEYS.AUTHORIZATION] = `Bearer ${credential}`;
898
+ }
899
+ if (options.idempotent === true)
900
+ headers[HEADER_KEYS.IDEMPOTENCY_KEY] = options.idempotencyKey ?? IdempotencyKey();
901
+ return headers;
902
+ }
903
+ async attempt(url, method, headers, body, signal) {
904
+ const deadline = Deadline(this.timeoutMs, signal);
905
+ const Failure = (error, fallback) => {
906
+ if (signal?.aborted)
907
+ return new OpenEmailNetworkError('Request aborted by the caller.', error);
908
+ if (deadline.timedOut())
909
+ return new OpenEmailNetworkError(`Request timed out after ${this.timeoutMs}ms.`, error, true);
910
+ return new OpenEmailNetworkError(`${fallback}: ${Describe(error)}`, error);
911
+ };
912
+ try {
913
+ let response;
914
+ try {
915
+ response = await this.fetchImpl(url, { method, headers, body, signal: deadline.signal });
916
+ }
917
+ catch (error) {
918
+ return Failure(error, `Could not reach ${this.baseUrl}`);
919
+ }
920
+ try {
921
+ return { response, text: await response.text() };
922
+ }
923
+ catch (error) {
924
+ if (response.ok || signal?.aborted || deadline.timedOut())
925
+ return Failure(error, 'The response body could not be read');
926
+ return { response, text: '' };
927
+ }
928
+ }
929
+ finally {
930
+ deadline.release();
931
+ }
932
+ }
933
+ parse(response, text, accept) {
934
+ if (accept && accept !== CONTENT_TYPES.JSON)
935
+ return text;
936
+ if (!text)
937
+ return undefined;
938
+ const parsed = ParseJson(text);
939
+ if (parsed !== undefined)
940
+ return parsed;
941
+ throw new OpenEmailApiError({
942
+ message: `The API answered ${response.status} with a body that is not JSON.`,
943
+ status: response.status,
944
+ type: ERROR_TYPE.API,
945
+ code: ERROR_CODES.UNRECOGNISED_RESPONSE,
946
+ requestId: response.headers.get(HEADER_KEYS.REQUEST_ID) ?? undefined
947
+ });
948
+ }
949
+ }
950
+
951
+ const Addresses = (transport) => ({
952
+ list: async (options = {}) => {
953
+ const envelope = await transport.request(PATHS.ADDRESSES, { signal: options.signal, apiKey: options.apiKey });
954
+ return {
955
+ unrestricted: envelope.unrestricted,
956
+ addresses: envelope.data ?? [],
957
+ domains: envelope.domains ?? []
958
+ };
959
+ }
960
+ });
961
+
962
+ const FetchPage = async (transport, path, style, options = {}, query = {}) => {
963
+ const payload = await transport.request(path, {
964
+ query: { ...query, [QUERY_KEYS.LIMIT]: options.limit, [style.PARAM]: options.cursor },
965
+ signal: options.signal,
966
+ apiKey: options.apiKey
967
+ });
968
+ const next = payload?.[style.FIELD];
969
+ const nextCursor = typeof next === 'string' && next.length > 0 ? next : null;
970
+ return {
971
+ items: (payload?.data ?? []),
972
+ hasMore: typeof payload?.hasMore === 'boolean' ? payload.hasMore : nextCursor !== null,
973
+ nextCursor
974
+ };
975
+ };
976
+ const IteratePages = async function* (transport, path, style, options = {}, query = {}) {
977
+ let cursor = options.cursor;
978
+ for (;;) {
979
+ const page = await FetchPage(transport, path, style, { ...options, cursor }, query);
980
+ for (const item of page.items)
981
+ yield item;
982
+ if (!page.hasMore || !page.nextCursor || page.nextCursor === cursor)
983
+ return;
984
+ cursor = page.nextCursor;
985
+ }
986
+ };
987
+ const CollectAll = async (transport, path, style, options = {}, query = {}) => {
988
+ const all = [];
989
+ for await (const item of IteratePages(transport, path, style, options, query))
990
+ all.push(item);
991
+ return all;
992
+ };
993
+ const FetchList = async (transport, path, options = {}, query) => {
994
+ const payload = await transport.request(path, {
995
+ query,
996
+ signal: options.signal,
997
+ apiKey: options.apiKey
998
+ });
999
+ return (payload?.data ?? []);
1000
+ };
1001
+
1002
+ const Segment = (value) => {
1003
+ if (typeof value !== 'string' || value.length === 0)
1004
+ throw new Error(CLIENT_MESSAGES.EMPTY_SEGMENT);
1005
+ if (/^\.+$/.test(value))
1006
+ throw new Error(`${JSON.stringify(value)} ${CLIENT_MESSAGES.DOT_SEGMENT}`);
1007
+ return encodeURIComponent(value);
1008
+ };
1009
+ const EmailPath = (id) => `${PATHS.EMAILS}/${Segment(id)}`;
1010
+ const EmailChildPath = (id, segment) => `${EmailPath(id)}/${segment}`;
1011
+ const ThreadPath = (id) => `${PATHS.THREADS}/${Segment(id)}`;
1012
+ const ThreadChildPath = (id, segment) => `${ThreadPath(id)}/${segment}`;
1013
+ const ThreadMessageAttachmentsPath = (id, messageId) => `${ThreadPath(id)}/${SEGMENTS.MESSAGES}/${Segment(messageId)}/${SEGMENTS.ATTACHMENTS}`;
1014
+ const DraftPath = (id) => `${PATHS.DRAFTS}/${Segment(id)}`;
1015
+ const LabelPath = (id) => `${PATHS.LABELS}/${Segment(id)}`;
1016
+ const ContactPath = (email) => `${PATHS.CONTACTS}/${Segment(email)}`;
1017
+ const DomainPath = (id) => `${PATHS.DOMAINS}/${Segment(id)}`;
1018
+ const WebhookPath = (id) => `${PATHS.WEBHOOKS}/${Segment(id)}`;
1019
+ const WebhookChildPath = (id, segment) => `${WebhookPath(id)}/${segment}`;
1020
+ const TemplatePath = (idOrSlug) => `${PATHS.TEMPLATES}/${Segment(idOrSlug)}`;
1021
+ const TemplateChildPath = (idOrSlug, segment) => `${TemplatePath(idOrSlug)}/${segment}`;
1022
+ const RulePath = (id) => `${PATHS.RULES}/${Segment(id)}`;
1023
+ const RuleChildPath = (id, segment) => `${RulePath(id)}/${segment}`;
1024
+ const TrackingPath = (id) => `${PATHS.TRACKING}/${Segment(id)}`;
1025
+ const TrackingChildPath = (id, segment) => `${TrackingPath(id)}/${segment}`;
1026
+ const CalendarEventPath = (id) => `${PATHS.CALENDAR_EVENTS}/${Segment(id)}`;
1027
+ const CalendarEventIcsPath = (id) => `${CalendarEventPath(id)}/${SEGMENTS.ICS}`;
1028
+ const RolePath = (id) => `${PATHS.ROLES}/${Segment(id)}`;
1029
+ const MemberPath = (userId) => `${PATHS.MEMBERS}/${Segment(userId)}`;
1030
+ const MemberAddressesPath = (userId) => `${MemberPath(userId)}/${SEGMENTS.ADDRESSES}`;
1031
+ const MemberAddressPath = (userId, addressId) => `${MemberAddressesPath(userId)}/${Segment(addressId)}`;
1032
+ const TempInboxPath = (id) => `${PATHS.TEMP_MAIL_INBOXES}/${Segment(id)}`;
1033
+ const TempInboxExtendPath = (id) => `${TempInboxPath(id)}/${SEGMENTS.EXTEND}`;
1034
+ const TempMessagesPath = (id) => `${TempInboxPath(id)}/${SEGMENTS.MESSAGES}`;
1035
+ const TempMessagePath = (id, messageId) => `${TempMessagesPath(id)}/${Segment(messageId)}`;
1036
+ const TempMessageAttachmentsPath = (id, messageId) => `${TempMessagePath(id, messageId)}/${SEGMENTS.ATTACHMENTS}`;
1037
+
1038
+ const ToBase64 = (bytes) => {
1039
+ const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
1040
+ let binary = '';
1041
+ for (let offset = 0; offset < view.length; offset += BASE64_CHUNK_SIZE) {
1042
+ binary += String.fromCharCode(...view.subarray(offset, offset + BASE64_CHUNK_SIZE));
1043
+ }
1044
+ return btoa(binary);
1045
+ };
1046
+
1047
+ const Many = (value) => {
1048
+ if (value === undefined)
1049
+ return undefined;
1050
+ return Array.isArray(value) ? value : [value];
1051
+ };
1052
+ const Instant = (value) => value instanceof Date ? value.toISOString() : value;
1053
+ const Attachment = (attachment) => ({
1054
+ filename: attachment.filename,
1055
+ content: typeof attachment.content === 'string' ? attachment.content : ToBase64(attachment.content),
1056
+ ...(attachment.contentType === undefined ? {} : { contentType: attachment.contentType })
1057
+ });
1058
+ const EmailToWire = (body) => ({
1059
+ ...body,
1060
+ to: Many(body.to),
1061
+ cc: Many(body.cc),
1062
+ bcc: Many(body.bcc),
1063
+ attachments: body.attachments?.map(Attachment),
1064
+ scheduledAt: Instant(body.scheduledAt)
1065
+ });
1066
+ const TemplateSendToWire = (body) => ({
1067
+ ...body,
1068
+ to: Many(body.to),
1069
+ cc: Many(body.cc),
1070
+ bcc: Many(body.bcc),
1071
+ scheduledAt: Instant(body.scheduledAt)
1072
+ });
1073
+ const ToInstant = Instant;
1074
+
1075
+ const Calendar = (transport) => ({
1076
+ listEvents: (options) => FetchList(transport, PATHS.CALENDAR_EVENTS, options, {
1077
+ [QUERY_KEYS.FROM]: ToInstant(options.from),
1078
+ [QUERY_KEYS.TO]: ToInstant(options.to),
1079
+ [QUERY_KEYS.TIMEZONE]: options.timezone
1080
+ }),
1081
+ getEvent: (id, options = {}) => transport.request(CalendarEventPath(id), { signal: options.signal, apiKey: options.apiKey }),
1082
+ getEventIcs: (id, options = {}) => transport.request(CalendarEventIcsPath(id), {
1083
+ accept: CONTENT_TYPES.CALENDAR,
1084
+ signal: options.signal,
1085
+ apiKey: options.apiKey
1086
+ })
1087
+ });
1088
+
1089
+ const Contacts = (transport) => ({
1090
+ list: (options = {}) => FetchList(transport, PATHS.CONTACTS, options, { [QUERY_KEYS.LIMIT]: options.limit }),
1091
+ get: (email, options = {}) => transport.request(ContactPath(email), { signal: options.signal, apiKey: options.apiKey })
1092
+ });
1093
+
1094
+ const Domains = (transport) => ({
1095
+ list: (options = {}) => FetchList(transport, PATHS.DOMAINS, options),
1096
+ get: (id, options = {}) => transport.request(DomainPath(id), { signal: options.signal, apiKey: options.apiKey })
1097
+ });
1098
+
1099
+ const ListQuery$4 = (options) => ({ [QUERY_KEYS.QUERY]: options.query });
1100
+ const Drafts = (transport) => ({
1101
+ list: (options = {}) => FetchPage(transport, PATHS.DRAFTS, CURSOR_STYLES.PAGE_TOKEN, options, ListQuery$4(options)),
1102
+ listAll: (options = {}) => CollectAll(transport, PATHS.DRAFTS, CURSOR_STYLES.PAGE_TOKEN, options, ListQuery$4(options)),
1103
+ iterate: (options = {}) => IteratePages(transport, PATHS.DRAFTS, CURSOR_STYLES.PAGE_TOKEN, options, ListQuery$4(options)),
1104
+ get: (id, options = {}) => transport.request(DraftPath(id), { signal: options.signal, apiKey: options.apiKey }),
1105
+ create: (body, options = {}) => transport.request(PATHS.DRAFTS, {
1106
+ method: HTTP_METHODS.POST,
1107
+ body,
1108
+ signal: options.signal,
1109
+ apiKey: options.apiKey
1110
+ }),
1111
+ update: (id, body, options = {}) => transport.request(DraftPath(id), {
1112
+ method: HTTP_METHODS.PATCH,
1113
+ body,
1114
+ signal: options.signal,
1115
+ apiKey: options.apiKey
1116
+ }),
1117
+ delete: (id, options = {}) => transport.request(DraftPath(id), {
1118
+ method: HTTP_METHODS.DELETE,
1119
+ signal: options.signal,
1120
+ apiKey: options.apiKey
1121
+ })
1122
+ });
1123
+
1124
+ const ListQuery$3 = (options) => ({
1125
+ [QUERY_KEYS.STATUS]: Array.isArray(options.status) ? options.status.join(LIST_SEPARATOR) : options.status,
1126
+ [QUERY_KEYS.FROM]: options.from
1127
+ });
1128
+ const Emails = (transport) => ({
1129
+ send: (body, options = {}) => transport.request(PATHS.EMAILS, {
1130
+ method: HTTP_METHODS.POST,
1131
+ body: EmailToWire(body),
1132
+ idempotent: true,
1133
+ idempotencyKey: options.idempotencyKey,
1134
+ repeatable: true,
1135
+ signal: options.signal,
1136
+ apiKey: options.apiKey
1137
+ }),
1138
+ sendBatch: async (emails, options = {}) => {
1139
+ const result = await transport.request(PATHS.EMAILS_BATCH, {
1140
+ method: HTTP_METHODS.POST,
1141
+ body: { emails: emails.map(EmailToWire) },
1142
+ idempotent: true,
1143
+ idempotencyKey: options.idempotencyKey,
1144
+ repeatable: true,
1145
+ signal: options.signal,
1146
+ apiKey: options.apiKey
1147
+ });
1148
+ return { items: result.data, sent: result.sent, failed: result.failed };
1149
+ },
1150
+ translate: (body, options = {}) => transport.request(PATHS.EMAILS_TRANSLATE, {
1151
+ method: HTTP_METHODS.POST,
1152
+ body,
1153
+ signal: options.signal,
1154
+ apiKey: options.apiKey
1155
+ }),
1156
+ list: (options = {}) => FetchPage(transport, PATHS.EMAILS, CURSOR_STYLES.CURSOR, options, ListQuery$3(options)),
1157
+ listAll: (options = {}) => CollectAll(transport, PATHS.EMAILS, CURSOR_STYLES.CURSOR, options, ListQuery$3(options)),
1158
+ iterate: (options = {}) => IteratePages(transport, PATHS.EMAILS, CURSOR_STYLES.CURSOR, options, ListQuery$3(options)),
1159
+ get: (id, options = {}) => transport.request(EmailPath(id), { signal: options.signal, apiKey: options.apiKey }),
1160
+ listEvents: (id, options = {}) => FetchList(transport, EmailChildPath(id, SEGMENTS.EVENTS), options),
1161
+ getTracking: (id, options = {}) => transport.request(EmailChildPath(id, SEGMENTS.TRACKING), { signal: options.signal, apiKey: options.apiKey }),
1162
+ cancel: (id, options = {}) => transport.request(EmailChildPath(id, SEGMENTS.CANCEL), {
1163
+ method: HTTP_METHODS.POST,
1164
+ repeatable: true,
1165
+ signal: options.signal,
1166
+ apiKey: options.apiKey
1167
+ }),
1168
+ reschedule: (id, scheduledAt, options = {}) => transport.request(EmailPath(id), {
1169
+ method: HTTP_METHODS.PATCH,
1170
+ body: { scheduledAt: ToInstant(scheduledAt) },
1171
+ repeatable: true,
1172
+ signal: options.signal,
1173
+ apiKey: options.apiKey
1174
+ })
1175
+ });
1176
+
1177
+ const Labels = (transport) => ({
1178
+ list: (options = {}) => FetchList(transport, PATHS.LABELS, options),
1179
+ get: (id, options = {}) => transport.request(LabelPath(id), { signal: options.signal, apiKey: options.apiKey }),
1180
+ create: (body, options = {}) => transport.request(PATHS.LABELS, {
1181
+ method: HTTP_METHODS.POST,
1182
+ body,
1183
+ signal: options.signal,
1184
+ apiKey: options.apiKey
1185
+ }),
1186
+ update: (id, body, options = {}) => transport.request(LabelPath(id), {
1187
+ method: HTTP_METHODS.PATCH,
1188
+ body,
1189
+ repeatable: true,
1190
+ signal: options.signal,
1191
+ apiKey: options.apiKey
1192
+ }),
1193
+ delete: (id, options = {}) => transport.request(LabelPath(id), {
1194
+ method: HTTP_METHODS.DELETE,
1195
+ signal: options.signal,
1196
+ apiKey: options.apiKey
1197
+ })
1198
+ });
1199
+
1200
+ const Languages = (transport) => ({
1201
+ list: (options = {}) => FetchList(transport, PATHS.LANGUAGES, options)
1202
+ });
1203
+
1204
+ const Me = (transport) => ({
1205
+ get: (options = {}) => transport.request(PATHS.KEYS_SELF, { signal: options.signal, apiKey: options.apiKey }),
1206
+ ping: (options = {}) => transport.request(PATHS.PING, { signal: options.signal, apiKey: options.apiKey })
1207
+ });
1208
+
1209
+ const Members = (transport) => ({
1210
+ list: (options = {}) => FetchList(transport, PATHS.MEMBERS, options),
1211
+ get: (userId, options = {}) => transport.request(MemberPath(userId), { signal: options.signal, apiKey: options.apiKey }),
1212
+ add: (body, options = {}) => transport.request(PATHS.MEMBERS, {
1213
+ method: HTTP_METHODS.POST,
1214
+ body,
1215
+ signal: options.signal,
1216
+ apiKey: options.apiKey
1217
+ }),
1218
+ update: (userId, patch, options = {}) => transport.request(MemberPath(userId), {
1219
+ method: HTTP_METHODS.PATCH,
1220
+ body: patch,
1221
+ repeatable: true,
1222
+ signal: options.signal,
1223
+ apiKey: options.apiKey
1224
+ }),
1225
+ remove: (userId, options = {}) => transport.request(MemberPath(userId), {
1226
+ method: HTTP_METHODS.DELETE,
1227
+ signal: options.signal,
1228
+ apiKey: options.apiKey
1229
+ }),
1230
+ grantAddress: (userId, body, options = {}) => transport.request(MemberAddressesPath(userId), {
1231
+ method: HTTP_METHODS.POST,
1232
+ body,
1233
+ repeatable: true,
1234
+ signal: options.signal,
1235
+ apiKey: options.apiKey
1236
+ }),
1237
+ revokeAddress: (userId, addressId, options = {}) => transport.request(MemberAddressPath(userId, addressId), {
1238
+ method: HTTP_METHODS.DELETE,
1239
+ signal: options.signal,
1240
+ apiKey: options.apiKey
1241
+ })
1242
+ });
1243
+
1244
+ const Roles = (transport) => ({
1245
+ list: (options = {}) => FetchList(transport, PATHS.ROLES, options),
1246
+ get: (id, options = {}) => transport.request(RolePath(id), { signal: options.signal, apiKey: options.apiKey }),
1247
+ create: (body, options = {}) => transport.request(PATHS.ROLES, {
1248
+ method: HTTP_METHODS.POST,
1249
+ body,
1250
+ signal: options.signal,
1251
+ apiKey: options.apiKey
1252
+ }),
1253
+ update: (id, patch, options = {}) => transport.request(RolePath(id), {
1254
+ method: HTTP_METHODS.PATCH,
1255
+ body: patch,
1256
+ repeatable: true,
1257
+ signal: options.signal,
1258
+ apiKey: options.apiKey
1259
+ }),
1260
+ delete: (id, options = {}) => transport.request(RolePath(id), {
1261
+ method: HTTP_METHODS.DELETE,
1262
+ query: { [QUERY_KEYS.REASSIGN_TO]: options.reassignTo },
1263
+ signal: options.signal,
1264
+ apiKey: options.apiKey
1265
+ }),
1266
+ listPermissions: (options = {}) => FetchList(transport, PATHS.ROLES_PERMISSIONS, options)
1267
+ });
1268
+
1269
+ const ListQuery$2 = (options) => ({
1270
+ [QUERY_KEYS.ENABLED]: options.enabled === undefined ? undefined : String(options.enabled)
1271
+ });
1272
+ const RunsQuery = (options) => ({
1273
+ [QUERY_KEYS.RULE_ID]: options.ruleId,
1274
+ [QUERY_KEYS.THREAD_ID]: options.threadId
1275
+ });
1276
+ const Rules = (transport) => ({
1277
+ list: (options = {}) => FetchPage(transport, PATHS.RULES, CURSOR_STYLES.CURSOR, options, ListQuery$2(options)),
1278
+ listAll: (options = {}) => CollectAll(transport, PATHS.RULES, CURSOR_STYLES.CURSOR, options, ListQuery$2(options)),
1279
+ iterate: (options = {}) => IteratePages(transport, PATHS.RULES, CURSOR_STYLES.CURSOR, options, ListQuery$2(options)),
1280
+ get: (id, options = {}) => transport.request(RulePath(id), { signal: options.signal, apiKey: options.apiKey }),
1281
+ create: (body, options = {}) => transport.request(PATHS.RULES, {
1282
+ method: HTTP_METHODS.POST,
1283
+ body,
1284
+ signal: options.signal,
1285
+ apiKey: options.apiKey
1286
+ }),
1287
+ update: (id, patch, options = {}) => transport.request(RulePath(id), {
1288
+ method: HTTP_METHODS.PATCH,
1289
+ body: patch,
1290
+ signal: options.signal,
1291
+ apiKey: options.apiKey
1292
+ }),
1293
+ delete: (id, options = {}) => transport.request(RulePath(id), {
1294
+ method: HTTP_METHODS.DELETE,
1295
+ signal: options.signal,
1296
+ apiKey: options.apiKey
1297
+ }),
1298
+ reorder: async (ruleIds, options = {}) => {
1299
+ const envelope = await transport.request(PATHS.RULES_REORDER, {
1300
+ method: HTTP_METHODS.POST,
1301
+ body: { ruleIds },
1302
+ repeatable: true,
1303
+ signal: options.signal,
1304
+ apiKey: options.apiKey
1305
+ });
1306
+ return envelope?.data ?? [];
1307
+ },
1308
+ test: (id, body = {}, options = {}) => transport.request(RuleChildPath(id, SEGMENTS.TEST), {
1309
+ method: HTTP_METHODS.POST,
1310
+ body,
1311
+ repeatable: true,
1312
+ signal: options.signal,
1313
+ apiKey: options.apiKey
1314
+ }),
1315
+ listRuns: (options = {}) => FetchPage(transport, PATHS.RULES_RUNS, CURSOR_STYLES.CURSOR, options, RunsQuery(options)),
1316
+ listAllRuns: (options = {}) => CollectAll(transport, PATHS.RULES_RUNS, CURSOR_STYLES.CURSOR, options, RunsQuery(options)),
1317
+ iterateRuns: (options = {}) => IteratePages(transport, PATHS.RULES_RUNS, CURSOR_STYLES.CURSOR, options, RunsQuery(options))
1318
+ });
1319
+
1320
+ const Settings = (transport) => ({
1321
+ get: (options = {}) => transport.request(PATHS.SETTINGS, { signal: options.signal, apiKey: options.apiKey }),
1322
+ update: (patch, options = {}) => transport.request(PATHS.SETTINGS, {
1323
+ method: HTTP_METHODS.PATCH,
1324
+ body: patch,
1325
+ repeatable: true,
1326
+ signal: options.signal,
1327
+ apiKey: options.apiKey
1328
+ })
1329
+ });
1330
+
1331
+ const TempMail = (transport) => ({
1332
+ listDomains: async (options = {}) => (await transport.request(PATHS.TEMP_MAIL_DOMAINS, {
1333
+ anonymous: true,
1334
+ signal: options.signal
1335
+ }))?.data ?? [],
1336
+ create: (body = {}, options = {}) => transport.request(PATHS.TEMP_MAIL_INBOXES, {
1337
+ method: HTTP_METHODS.POST,
1338
+ body,
1339
+ anonymous: true,
1340
+ signal: options.signal
1341
+ }),
1342
+ get: (inboxId, options = {}) => transport.request(TempInboxPath(inboxId), {
1343
+ inboxToken: options.inboxToken,
1344
+ signal: options.signal
1345
+ }),
1346
+ extend: (inboxId, options = {}) => transport.request(TempInboxExtendPath(inboxId), {
1347
+ method: HTTP_METHODS.POST,
1348
+ inboxToken: options.inboxToken,
1349
+ signal: options.signal
1350
+ }),
1351
+ delete: (inboxId, options = {}) => transport.request(TempInboxPath(inboxId), {
1352
+ method: HTTP_METHODS.DELETE,
1353
+ inboxToken: options.inboxToken,
1354
+ signal: options.signal
1355
+ }),
1356
+ listMessages: async (inboxId, options = {}) => {
1357
+ const envelope = await transport.request(TempMessagesPath(inboxId), {
1358
+ query: { [QUERY_KEYS.LIMIT]: options.limit },
1359
+ inboxToken: options.inboxToken,
1360
+ signal: options.signal
1361
+ });
1362
+ return { items: envelope?.data ?? [], expiresAt: envelope?.expiresAt };
1363
+ },
1364
+ getMessage: (inboxId, messageId, options = {}) => transport.request(TempMessagePath(inboxId, messageId), {
1365
+ inboxToken: options.inboxToken,
1366
+ signal: options.signal
1367
+ }),
1368
+ deleteMessage: (inboxId, messageId, options = {}) => transport.request(TempMessagePath(inboxId, messageId), {
1369
+ method: HTTP_METHODS.DELETE,
1370
+ inboxToken: options.inboxToken,
1371
+ signal: options.signal
1372
+ }),
1373
+ listAttachments: async (inboxId, messageId, options = {}) => (await transport.request(TempMessageAttachmentsPath(inboxId, messageId), {
1374
+ inboxToken: options.inboxToken,
1375
+ signal: options.signal
1376
+ }))?.data ?? []
1377
+ });
1378
+
1379
+ const ListQuery$1 = (options) => ({ [QUERY_KEYS.STATUS]: options.status });
1380
+ const Templates = (transport) => ({
1381
+ list: (options = {}) => FetchPage(transport, PATHS.TEMPLATES, CURSOR_STYLES.CURSOR, options, ListQuery$1(options)),
1382
+ listAll: (options = {}) => CollectAll(transport, PATHS.TEMPLATES, CURSOR_STYLES.CURSOR, options, ListQuery$1(options)),
1383
+ iterate: (options = {}) => IteratePages(transport, PATHS.TEMPLATES, CURSOR_STYLES.CURSOR, options, ListQuery$1(options)),
1384
+ get: (idOrSlug, options = {}) => transport.request(TemplatePath(idOrSlug), { signal: options.signal, apiKey: options.apiKey }),
1385
+ create: (body, options = {}) => transport.request(PATHS.TEMPLATES, {
1386
+ method: HTTP_METHODS.POST,
1387
+ body,
1388
+ signal: options.signal,
1389
+ apiKey: options.apiKey
1390
+ }),
1391
+ update: (idOrSlug, patch, options = {}) => transport.request(TemplatePath(idOrSlug), {
1392
+ method: HTTP_METHODS.PATCH,
1393
+ body: patch,
1394
+ signal: options.signal,
1395
+ apiKey: options.apiKey
1396
+ }),
1397
+ delete: (idOrSlug, options = {}) => transport.request(TemplatePath(idOrSlug), {
1398
+ method: HTTP_METHODS.DELETE,
1399
+ signal: options.signal,
1400
+ apiKey: options.apiKey
1401
+ }),
1402
+ listVersions: (idOrSlug, options = {}) => FetchList(transport, TemplateChildPath(idOrSlug, SEGMENTS.VERSIONS), options),
1403
+ publish: (idOrSlug, options = {}) => transport.request(TemplateChildPath(idOrSlug, SEGMENTS.VERSIONS), {
1404
+ method: HTTP_METHODS.POST,
1405
+ repeatable: true,
1406
+ signal: options.signal,
1407
+ apiKey: options.apiKey
1408
+ }),
1409
+ preview: (idOrSlug, body = {}, options = {}) => transport.request(TemplateChildPath(idOrSlug, SEGMENTS.PREVIEW), {
1410
+ method: HTTP_METHODS.POST,
1411
+ body,
1412
+ repeatable: true,
1413
+ signal: options.signal,
1414
+ apiKey: options.apiKey
1415
+ }),
1416
+ send: (idOrSlug, body, options = {}) => transport.request(TemplateChildPath(idOrSlug, SEGMENTS.SEND), {
1417
+ method: HTTP_METHODS.POST,
1418
+ body: TemplateSendToWire(body),
1419
+ idempotent: true,
1420
+ idempotencyKey: options.idempotencyKey,
1421
+ repeatable: true,
1422
+ signal: options.signal,
1423
+ apiKey: options.apiKey
1424
+ })
1425
+ });
1426
+
1427
+ const ListQuery = (options) => ({
1428
+ [QUERY_KEYS.FOLDER]: options.folder,
1429
+ [QUERY_KEYS.QUERY]: options.query,
1430
+ [QUERY_KEYS.LABEL_IDS]: Array.isArray(options.labelIds) ? options.labelIds.join(LIST_SEPARATOR) : options.labelIds
1431
+ });
1432
+ const Threads = (transport) => ({
1433
+ list: (options = {}) => FetchPage(transport, PATHS.THREADS, CURSOR_STYLES.PAGE_TOKEN, options, ListQuery(options)),
1434
+ listAll: (options = {}) => CollectAll(transport, PATHS.THREADS, CURSOR_STYLES.PAGE_TOKEN, options, ListQuery(options)),
1435
+ iterate: (options = {}) => IteratePages(transport, PATHS.THREADS, CURSOR_STYLES.PAGE_TOKEN, options, ListQuery(options)),
1436
+ get: (id, options = {}) => transport.request(ThreadPath(id), { signal: options.signal, apiKey: options.apiKey }),
1437
+ update: (id, patch, options = {}) => transport.request(ThreadPath(id), {
1438
+ method: HTTP_METHODS.PATCH,
1439
+ body: patch,
1440
+ repeatable: true,
1441
+ signal: options.signal,
1442
+ apiKey: options.apiKey
1443
+ }),
1444
+ trash: (id, options = {}) => transport.request(ThreadChildPath(id, SEGMENTS.TRASH), {
1445
+ method: HTTP_METHODS.POST,
1446
+ repeatable: true,
1447
+ signal: options.signal,
1448
+ apiKey: options.apiKey
1449
+ }),
1450
+ snooze: (id, wakeAt, options = {}) => transport.request(ThreadChildPath(id, SEGMENTS.SNOOZE), {
1451
+ method: HTTP_METHODS.POST,
1452
+ body: { wakeAt: ToInstant(wakeAt) },
1453
+ repeatable: true,
1454
+ signal: options.signal,
1455
+ apiKey: options.apiKey
1456
+ }),
1457
+ unsnooze: (id, options = {}) => transport.request(ThreadChildPath(id, SEGMENTS.UNSNOOZE), {
1458
+ method: HTTP_METHODS.POST,
1459
+ repeatable: true,
1460
+ signal: options.signal,
1461
+ apiKey: options.apiKey
1462
+ }),
1463
+ listAttachments: (id, messageId, options = {}) => FetchList(transport, ThreadMessageAttachmentsPath(id, messageId), options)
1464
+ });
1465
+
1466
+ const Flag = (value) => value === undefined ? undefined : String(value);
1467
+ const HitQuery = (options) => ({
1468
+ [QUERY_KEYS.INCLUDE_MACHINE]: Flag(options.includeMachine),
1469
+ [QUERY_KEYS.LIMIT]: options.limit
1470
+ });
1471
+ const Tracking = (transport) => ({
1472
+ list: (options = {}) => FetchList(transport, PATHS.TRACKING, options, {
1473
+ [QUERY_KEYS.OPENED]: Flag(options.opened),
1474
+ [QUERY_KEYS.CLICKED]: Flag(options.clicked),
1475
+ [QUERY_KEYS.DAYS]: options.days,
1476
+ [QUERY_KEYS.MINUTES]: options.minutes,
1477
+ [QUERY_KEYS.GRAIN]: options.grain,
1478
+ [QUERY_KEYS.LIMIT]: options.limit
1479
+ }),
1480
+ getStats: (options = {}) => transport.request(PATHS.TRACKING_STATS, {
1481
+ query: {
1482
+ [QUERY_KEYS.DAYS]: options.days,
1483
+ [QUERY_KEYS.MINUTES]: options.minutes,
1484
+ [QUERY_KEYS.GRAIN]: options.grain,
1485
+ [QUERY_KEYS.OFFSET_MINUTES]: options.offsetMinutes
1486
+ },
1487
+ signal: options.signal,
1488
+ apiKey: options.apiKey
1489
+ }),
1490
+ get: (id, options = {}) => transport.request(TrackingPath(id), { signal: options.signal, apiKey: options.apiKey }),
1491
+ listOpens: (id, options = {}) => FetchList(transport, TrackingChildPath(id, SEGMENTS.OPENS), options, HitQuery(options)),
1492
+ listClicks: (id, options = {}) => FetchList(transport, TrackingChildPath(id, SEGMENTS.CLICKS), options, HitQuery(options))
1493
+ });
1494
+
1495
+ const Webhooks = (transport) => ({
1496
+ list: (options = {}) => FetchList(transport, PATHS.WEBHOOKS, options),
1497
+ get: (id, options = {}) => transport.request(WebhookPath(id), { signal: options.signal, apiKey: options.apiKey }),
1498
+ create: (body, options = {}) => transport.request(PATHS.WEBHOOKS, {
1499
+ method: HTTP_METHODS.POST,
1500
+ body,
1501
+ signal: options.signal,
1502
+ apiKey: options.apiKey
1503
+ }),
1504
+ update: (id, patch, options = {}) => transport.request(WebhookPath(id), {
1505
+ method: HTTP_METHODS.PATCH,
1506
+ body: patch,
1507
+ repeatable: true,
1508
+ signal: options.signal,
1509
+ apiKey: options.apiKey
1510
+ }),
1511
+ delete: (id, options = {}) => transport.request(WebhookPath(id), {
1512
+ method: HTTP_METHODS.DELETE,
1513
+ signal: options.signal,
1514
+ apiKey: options.apiKey
1515
+ }),
1516
+ rotateSecret: (id, options = {}) => transport.request(WebhookChildPath(id, SEGMENTS.ROTATE_SECRET), {
1517
+ method: HTTP_METHODS.POST,
1518
+ signal: options.signal,
1519
+ apiKey: options.apiKey
1520
+ }),
1521
+ test: (id, options = {}) => transport.request(WebhookChildPath(id, SEGMENTS.TEST), {
1522
+ method: HTTP_METHODS.POST,
1523
+ signal: options.signal,
1524
+ apiKey: options.apiKey
1525
+ }),
1526
+ listDeliveries: (id, options = {}) => FetchList(transport, WebhookChildPath(id, SEGMENTS.DELIVERIES), options)
1527
+ });
1528
+
1529
+ class OpenEmail {
1530
+ transport;
1531
+ mode;
1532
+ me;
1533
+ addresses;
1534
+ languages;
1535
+ emails;
1536
+ templates;
1537
+ tracking;
1538
+ threads;
1539
+ drafts;
1540
+ labels;
1541
+ contacts;
1542
+ domains;
1543
+ rules;
1544
+ webhooks;
1545
+ calendar;
1546
+ settings;
1547
+ roles;
1548
+ members;
1549
+ tempMail;
1550
+ constructor(configuration) {
1551
+ const options = typeof configuration === 'string' ? { apiKey: configuration } : configuration;
1552
+ AssertApiKey(options.apiKey, '{ apiKey }');
1553
+ if (IsBrowser() && options.dangerouslyAllowBrowser !== true)
1554
+ throw new Error(BROWSER_REFUSAL);
1555
+ this.transport = new Transport(options);
1556
+ this.mode = ModeOf(options.apiKey);
1557
+ this.me = Me(this.transport);
1558
+ this.addresses = Addresses(this.transport);
1559
+ this.languages = Languages(this.transport);
1560
+ this.emails = Emails(this.transport);
1561
+ this.templates = Templates(this.transport);
1562
+ this.tracking = Tracking(this.transport);
1563
+ this.threads = Threads(this.transport);
1564
+ this.drafts = Drafts(this.transport);
1565
+ this.labels = Labels(this.transport);
1566
+ this.contacts = Contacts(this.transport);
1567
+ this.domains = Domains(this.transport);
1568
+ this.rules = Rules(this.transport);
1569
+ this.webhooks = Webhooks(this.transport);
1570
+ this.calendar = Calendar(this.transport);
1571
+ this.settings = Settings(this.transport);
1572
+ this.roles = Roles(this.transport);
1573
+ this.members = Members(this.transport);
1574
+ this.tempMail = TempMail(this.transport);
1575
+ }
1576
+ get raw() {
1577
+ return this.transport;
1578
+ }
1579
+ }
1580
+
1581
+ const FromEnvironment = (name) => {
1582
+ const scope = globalThis;
1583
+ try {
1584
+ const value = scope.process?.env?.[name] ?? scope.Deno?.env?.get(name);
1585
+ return value && value.length > 0 ? value : undefined;
1586
+ }
1587
+ catch {
1588
+ return undefined;
1589
+ }
1590
+ };
1591
+
1592
+ const NormaliseEndpoint = (value) => {
1593
+ if (!value)
1594
+ return undefined;
1595
+ const trimmed = value.trim().replace(/\/+$/, '');
1596
+ if (trimmed.length === 0)
1597
+ return undefined;
1598
+ if (ENDPOINT_SCHEME_PATTERN.test(trimmed))
1599
+ return trimmed;
1600
+ const host = trimmed.split(':')[0];
1601
+ return `${LOCAL_HOSTS.includes(host) ? 'http' : 'https'}://${trimmed}`;
1602
+ };
1603
+
1604
+ const CreateClient = (configuration = {}) => {
1605
+ const options = typeof configuration === 'string' ? { apiKey: configuration } : configuration;
1606
+ return new OpenEmail({
1607
+ ...options,
1608
+ apiKey: options.apiKey ?? FromEnvironment(ENV_VARS.API_KEY),
1609
+ baseUrl: NormaliseEndpoint(options.baseUrl ?? FromEnvironment(ENV_VARS.BASE_URL))
1610
+ });
1611
+ };
1612
+
1613
+ const CreateTempMail = (options = {}) => TempMail(new Transport({
1614
+ ...options,
1615
+ apiKey: undefined,
1616
+ baseUrl: NormaliseEndpoint(options.baseUrl ?? FromEnvironment(ENV_VARS.BASE_URL))
1617
+ }));
1618
+
1619
+ const IsSealed = (message) => !!message.encryption && SEALED_ENCRYPTION_FORMATS.includes(message.encryption.format);
1620
+
1621
+ const ReadHeader = (headers, name) => {
1622
+ if (typeof headers.get === 'function')
1623
+ return headers.get(name) ?? '';
1624
+ const wanted = name.toLowerCase();
1625
+ for (const [key, value] of Object.entries(headers)) {
1626
+ if (key.toLowerCase() !== wanted)
1627
+ continue;
1628
+ if (Array.isArray(value))
1629
+ return value[0] ?? '';
1630
+ return value ?? '';
1631
+ }
1632
+ return '';
1633
+ };
1634
+ const ParseSignature = (header) => {
1635
+ let timestamp = Number.NaN;
1636
+ const signatures = [];
1637
+ for (const pair of header.split(WEBHOOK_SIGNATURE.PAIR_SEPARATOR)) {
1638
+ const at = pair.indexOf(WEBHOOK_SIGNATURE.VALUE_SEPARATOR);
1639
+ if (at < 1)
1640
+ continue;
1641
+ const key = pair.slice(0, at).trim();
1642
+ const value = pair.slice(at + 1).trim();
1643
+ if (key === WEBHOOK_SIGNATURE.TIMESTAMP)
1644
+ timestamp = Number(value);
1645
+ if (key === WEBHOOK_SIGNATURE.VERSION && value)
1646
+ signatures.push(value);
1647
+ }
1648
+ if (!Number.isFinite(timestamp) || signatures.length === 0)
1649
+ return null;
1650
+ return { timestamp, signatures };
1651
+ };
1652
+ const Equal = (a, b) => {
1653
+ if (a.length !== b.length)
1654
+ return false;
1655
+ let mismatch = 0;
1656
+ for (let i = 0; i < a.length; i++)
1657
+ mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
1658
+ return mismatch === 0;
1659
+ };
1660
+ const Hex = (bytes) => Array.from(new Uint8Array(bytes), byte => byte.toString(16).padStart(2, '0')).join('');
1661
+ const VerifyWebhookSignature = async (props) => {
1662
+ const { payload, headers, secret, toleranceSeconds } = props;
1663
+ const subtle = globalThis.crypto?.subtle;
1664
+ if (!subtle)
1665
+ throw new Error(WEBHOOK_MESSAGES.NO_WEBCRYPTO);
1666
+ const header = ReadHeader(headers, WEBHOOK_HEADERS.SIGNATURE);
1667
+ if (!header)
1668
+ throw new Error(WEBHOOK_MESSAGES.MISSING_HEADER);
1669
+ const parsed = ParseSignature(header);
1670
+ if (!parsed)
1671
+ throw new Error(WEBHOOK_MESSAGES.MALFORMED_HEADER);
1672
+ const tolerance = toleranceSeconds ?? WEBHOOK_SIGNATURE.TOLERANCE_SECONDS;
1673
+ const drift = Math.abs(Math.floor(Date.now() / 1000) - parsed.timestamp);
1674
+ if (tolerance > 0 && drift > tolerance) {
1675
+ throw new Error(WEBHOOK_MESSAGES.OUT_OF_TOLERANCE.replace('{{drift}}', String(drift)).replace('{{tolerance}}', String(tolerance)));
1676
+ }
1677
+ const encoder = new TextEncoder();
1678
+ const key = await subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
1679
+ const expected = Hex(await subtle.sign('HMAC', key, encoder.encode(`${parsed.timestamp}.${payload}`)));
1680
+ if (!parsed.signatures.some(candidate => Equal(expected, candidate)))
1681
+ throw new Error(WEBHOOK_MESSAGES.MISMATCH);
1682
+ return JSON.parse(payload);
1683
+ };
1684
+
1685
+ const Init = (configuration = {}) => {
1686
+ const client = CreateClient(configuration);
1687
+ CLIENT_REGISTRY[CLIENT_REGISTRY_KEY] = client;
1688
+ return client;
1689
+ };
1690
+ const GetClient = () => {
1691
+ const existing = CLIENT_REGISTRY[CLIENT_REGISTRY_KEY];
1692
+ if (existing)
1693
+ return existing;
1694
+ return Init();
1695
+ };
1696
+ const ResetClient = () => {
1697
+ CLIENT_REGISTRY[CLIENT_REGISTRY_KEY] = null;
1698
+ };
1699
+ const DefaultClient = new Proxy({}, {
1700
+ get: (_target, property) => {
1701
+ if (typeof property === 'symbol' || property === 'then')
1702
+ return undefined;
1703
+ return Reflect.get(GetClient(), property);
1704
+ },
1705
+ has: (_target, property) => Reflect.has(GetClient(), property)
1706
+ });
1707
+
1708
+ const Key = (value) => value.trim().toLowerCase();
1709
+ const BY_CODE = new Map(LANGUAGES.map(language => [Key(language.code), language]));
1710
+ const BY_LABEL = new Map(LANGUAGES.map(language => [Key(language.label), language]));
1711
+ const BY_NATIVE = new Map(LANGUAGES.map(language => [Key(language.native), language]));
1712
+ const ALIASES = new Map(LANGUAGE_ALIASES.map(([alias, code]) => [Key(alias), Key(code)]));
1713
+ const LanguageByCode = (code) => code ? BY_CODE.get(Key(code)) : undefined;
1714
+ const ResolveLanguage = (input) => {
1715
+ if (!input)
1716
+ return undefined;
1717
+ const key = Key(input);
1718
+ if (!key)
1719
+ return undefined;
1720
+ const aliased = ALIASES.get(key);
1721
+ if (aliased)
1722
+ return BY_CODE.get(aliased);
1723
+ const direct = BY_CODE.get(key) ?? BY_LABEL.get(key) ?? BY_NATIVE.get(key);
1724
+ if (direct)
1725
+ return direct;
1726
+ const base = key.split(LANGUAGE_SUBTAG_PATTERN)[0];
1727
+ if (!base || base === key)
1728
+ return undefined;
1729
+ return BY_CODE.get(ALIASES.get(base) ?? base);
1730
+ };
1731
+ const IsRtlLanguage = (code) => ResolveLanguage(code)?.rtl === true;
1732
+
1733
+ const init = Init;
1734
+ const openemail = DefaultClient;
1735
+ const getClient = GetClient;
1736
+ const resetClient = ResetClient;
1737
+ const createOpenEmail = CreateClient;
1738
+ const createClient = CreateClient;
1739
+ const createTempMail = CreateTempMail;
1740
+ const verifyWebhookSignature = VerifyWebhookSignature;
1741
+ const toBase64 = ToBase64;
1742
+ const isApiKey = IsApiKey;
1743
+ const isSealed = IsSealed;
1744
+ const resolveLanguage = ResolveLanguage;
1745
+ const languageByCode = LanguageByCode;
1746
+ const isRtlLanguage = IsRtlLanguage;
1747
+
1748
+ exports.API_SCOPES = API_SCOPES;
1749
+ exports.ERROR_TYPES = ERROR_TYPES;
1750
+ exports.LANGUAGES = LANGUAGES;
1751
+ exports.MESSAGE_ENCRYPTION_FORMATS = MESSAGE_ENCRYPTION_FORMATS;
1752
+ exports.OpenEmail = OpenEmail;
1753
+ exports.OpenEmailApiError = OpenEmailApiError;
1754
+ exports.OpenEmailNetworkError = OpenEmailNetworkError;
1755
+ exports.PAGE_LIMITS = PAGE_LIMITS;
1756
+ exports.RULE_ACTIONS = RULE_ACTIONS;
1757
+ exports.RULE_FIELDS = RULE_FIELDS;
1758
+ exports.RULE_OPERATORS = RULE_OPERATORS;
1759
+ exports.VERSION = VERSION;
1760
+ exports.WEBHOOK_EVENTS = WEBHOOK_EVENTS;
1761
+ exports.WEBHOOK_SIGNATURE_HEADERS = WEBHOOK_SIGNATURE_HEADERS;
1762
+ exports.createClient = createClient;
1763
+ exports.createOpenEmail = createOpenEmail;
1764
+ exports.createTempMail = createTempMail;
1765
+ exports.default = OpenEmail;
1766
+ exports.getClient = getClient;
1767
+ exports.init = init;
1768
+ exports.isApiKey = isApiKey;
1769
+ exports.isRtlLanguage = isRtlLanguage;
1770
+ exports.isSealed = isSealed;
1771
+ exports.languageByCode = languageByCode;
1772
+ exports.openemail = openemail;
1773
+ exports.resetClient = resetClient;
1774
+ exports.resolveLanguage = resolveLanguage;
1775
+ exports.toBase64 = toBase64;
1776
+ exports.verifyWebhookSignature = verifyWebhookSignature;