@sendmailos/sdk 1.2.0 → 1.2.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.
package/dist/index.mjs CHANGED
@@ -54,9 +54,10 @@ var EmailsResource = class {
54
54
  }
55
55
  /**
56
56
  * Send a transactional email
57
- *
57
+ *
58
58
  * @example
59
59
  * ```ts
60
+ * // Business account (single-tenant)
60
61
  * const result = await client.emails.send({
61
62
  * to: 'user@example.com',
62
63
  * fromName: 'Your Company',
@@ -64,6 +65,16 @@ var EmailsResource = class {
64
65
  * subject: 'Welcome!',
65
66
  * html: '<h1>Hello!</h1>'
66
67
  * });
68
+ *
69
+ * // Agency account (multi-tenant) - identify client by domain
70
+ * const result = await agencyClient.emails.send({
71
+ * to: 'customer@example.com',
72
+ * fromName: 'Pizza Palace',
73
+ * fromEmail: 'hello@pizzapalace.com',
74
+ * subject: 'Your order is ready!',
75
+ * html: '<h1>Order Ready!</h1>',
76
+ * domain: 'pizzapalace.com' // Required for agency accounts
77
+ * });
67
78
  * ```
68
79
  */
69
80
  async send(params) {
@@ -77,13 +88,15 @@ var EmailsResource = class {
77
88
  from_name: params.fromName,
78
89
  from_email: params.fromEmail,
79
90
  variables: params.variables,
80
- type: params.type || "transactional"
91
+ type: params.type || "transactional",
92
+ workspace_id: params.workspaceId,
93
+ domain: params.domain
81
94
  })
82
95
  });
83
96
  }
84
97
  /**
85
98
  * Send email using a template
86
- *
99
+ *
87
100
  * @example
88
101
  * ```ts
89
102
  * const result = await client.emails.sendTemplate({
@@ -101,7 +114,9 @@ var EmailsResource = class {
101
114
  fromName: params.fromName,
102
115
  fromEmail: params.fromEmail,
103
116
  variables: params.variables,
104
- type: "transactional"
117
+ type: "transactional",
118
+ workspaceId: params.workspaceId,
119
+ domain: params.domain
105
120
  });
106
121
  }
107
122
  };
@@ -113,14 +128,23 @@ var SubscribersResource = class {
113
128
  }
114
129
  /**
115
130
  * Create or update a subscriber (upsert)
116
- *
131
+ *
117
132
  * @example
118
133
  * ```ts
134
+ * // Business account
119
135
  * const { subscriber } = await client.subscribers.create({
120
136
  * email: 'user@example.com',
121
137
  * firstName: 'John',
122
138
  * tags: ['newsletter', 'premium']
123
139
  * });
140
+ *
141
+ * // Agency account - subscriber tracked to client domain
142
+ * const { subscriber } = await agencyClient.subscribers.create({
143
+ * email: 'customer@example.com',
144
+ * firstName: 'Jane',
145
+ * domain: 'pizzapalace.com', // Required for agency accounts
146
+ * tags: ['loyalty-member']
147
+ * });
124
148
  * ```
125
149
  */
126
150
  async create(params) {
@@ -131,7 +155,10 @@ var SubscribersResource = class {
131
155
  first_name: params.firstName,
132
156
  last_name: params.lastName,
133
157
  tags: params.tags,
134
- domain_id: params.domainId
158
+ domain_id: params.domainId,
159
+ workspace_id: params.workspaceId,
160
+ domain: params.domain
161
+ // Alternative to workspace_id
135
162
  })
136
163
  });
137
164
  }
@@ -150,6 +177,8 @@ var SubscribersResource = class {
150
177
  const searchParams = new URLSearchParams();
151
178
  if (params.limit) searchParams.set("limit", String(params.limit));
152
179
  if (params.offset) searchParams.set("offset", String(params.offset));
180
+ if (params.workspaceId) searchParams.set("workspace_id", params.workspaceId);
181
+ if (params.domain) searchParams.set("domain", params.domain);
153
182
  const query = searchParams.toString();
154
183
  const endpoint = query ? `/subscribers?${query}` : "/subscribers";
155
184
  return this.request(endpoint, {
@@ -221,9 +250,10 @@ var CampaignsResource = class {
221
250
  }
222
251
  /**
223
252
  * Send a campaign to subscribers
224
- *
253
+ *
225
254
  * @example
226
255
  * ```ts
256
+ * // Business account
227
257
  * const result = await client.campaigns.send({
228
258
  * name: 'January Newsletter',
229
259
  * subject: 'What\'s new this month',
@@ -232,6 +262,17 @@ var CampaignsResource = class {
232
262
  * html: '<h1>Hello {{first_name}}!</h1>',
233
263
  * tags: ['newsletter', 'active']
234
264
  * });
265
+ *
266
+ * // Agency account - send to specific client's subscribers
267
+ * const result = await agencyClient.campaigns.send({
268
+ * name: 'Pizza Promo',
269
+ * subject: 'New menu items!',
270
+ * fromName: 'Pizza Palace',
271
+ * fromEmail: 'promo@pizzapalace.com',
272
+ * html: '<h1>Check out our new menu!</h1>',
273
+ * domain: 'pizzapalace.com', // Filter to this client's subscribers
274
+ * sourceDomains: ['pizzapalace.com'] // Only subscribers who signed up on this domain
275
+ * });
235
276
  * ```
236
277
  */
237
278
  async send(params) {
@@ -245,7 +286,10 @@ var CampaignsResource = class {
245
286
  html: params.html,
246
287
  templateId: params.templateId,
247
288
  tags: params.tags,
248
- variables: params.variables
289
+ variables: params.variables,
290
+ workspace_id: params.workspaceId,
291
+ domain: params.domain,
292
+ sourceDomains: params.sourceDomains
249
293
  })
250
294
  });
251
295
  }
@@ -260,7 +304,10 @@ var CampaignsResource = class {
260
304
  fromEmail: params.fromEmail,
261
305
  templateId: params.templateId,
262
306
  tags: params.tags,
263
- variables: params.variables
307
+ variables: params.variables,
308
+ workspaceId: params.workspaceId,
309
+ domain: params.domain,
310
+ sourceDomains: params.sourceDomains
264
311
  });
265
312
  }
266
313
  };
@@ -552,6 +599,400 @@ var WorkspacesResource = class {
552
599
  client_can_view_reports: true
553
600
  });
554
601
  }
602
+ /**
603
+ * Smart workspace provisioning - finds existing or creates new
604
+ *
605
+ * This is the recommended method for agency client onboarding.
606
+ * It will:
607
+ * 1. Search for existing workspace by website URL or client_email
608
+ * 2. Return existing workspace if found
609
+ * 3. Create new workspace with industry templates if not found
610
+ * 4. Optionally generate a workspace-scoped API key
611
+ *
612
+ * @note Only available for agency accounts
613
+ *
614
+ * @example
615
+ * ```ts
616
+ * // Auto-provision workspace for a new client
617
+ * const result = await client.workspaces.provision({
618
+ * name: 'Pizza Palace',
619
+ * website: 'https://pizzapalace.com',
620
+ * client_email: 'owner@pizzapalace.com',
621
+ * industry: 'restaurants',
622
+ * generate_api_key: true
623
+ * });
624
+ *
625
+ * if (result.is_new) {
626
+ * console.log('Created new workspace:', result.workspace.id);
627
+ * console.log('API Key:', result.api_key);
628
+ * } else {
629
+ * console.log('Found existing workspace:', result.workspace.id);
630
+ * }
631
+ * ```
632
+ *
633
+ * @example
634
+ * ```ts
635
+ * // Idempotent client setup - safe to call multiple times
636
+ * const { workspace, is_new } = await client.workspaces.provision({
637
+ * name: 'Client Name',
638
+ * website: 'https://client.com',
639
+ * industry: 'ecommerce'
640
+ * });
641
+ * // Always returns the same workspace for the same website
642
+ * ```
643
+ */
644
+ async provision(request) {
645
+ const response = await this.request("/workspaces/provision", {
646
+ method: "POST",
647
+ body: JSON.stringify(request)
648
+ });
649
+ return {
650
+ workspace: response.workspace,
651
+ is_new: response.is_new,
652
+ api_key: response.api_key,
653
+ message: response.message
654
+ };
655
+ }
656
+ /**
657
+ * Alias for provision() - find or create workspace
658
+ * @see provision
659
+ */
660
+ async findOrCreate(request) {
661
+ return this.provision(request);
662
+ }
663
+ /**
664
+ * Complete client onboarding in ONE call (Agency only)
665
+ *
666
+ * This is the simplest way to onboard a new client:
667
+ * 1. Creates or finds workspace by domain
668
+ * 2. Registers domain for email verification
669
+ * 3. Generates workspace token
670
+ * 4. Returns DNS records + token
671
+ *
672
+ * After this, use the returned `token` for all API calls.
673
+ *
674
+ * @example
675
+ * ```ts
676
+ * // Your SaaS onboards a new restaurant
677
+ * const result = await agency.workspaces.onboard({
678
+ * domain: 'pizzapalace.com',
679
+ * industry: 'restaurants'
680
+ * });
681
+ *
682
+ * // Save this token - it's all you need
683
+ * saveToDatabase(restaurantId, result.token);
684
+ *
685
+ * // Show DNS records to restaurant owner
686
+ * console.log('Add these DNS records:', result.dns_records);
687
+ *
688
+ * // Later, use the token directly
689
+ * const restaurant = new SendMailOS(result.token);
690
+ * await restaurant.emails.send({ ... });
691
+ * ```
692
+ */
693
+ async onboard(request) {
694
+ const response = await this.request("/workspaces/onboard", {
695
+ method: "POST",
696
+ body: JSON.stringify(request)
697
+ });
698
+ return {
699
+ token: response.token,
700
+ workspace_id: response.workspace_id,
701
+ workspace_name: response.workspace_name,
702
+ domain: response.domain,
703
+ domain_status: response.domain_status,
704
+ dns_records: response.dns_records,
705
+ is_new: response.is_new,
706
+ message: response.message
707
+ };
708
+ }
709
+ };
710
+
711
+ // src/resources/workflows.ts
712
+ var WorkflowsResource = class {
713
+ constructor(request) {
714
+ this.request = request;
715
+ }
716
+ /**
717
+ * Create a new workflow
718
+ *
719
+ * @example
720
+ * ```ts
721
+ * // Create workflow for a specific client (multi-brand)
722
+ * const result = await platform.workflows.create({
723
+ * name: 'Welcome Sequence',
724
+ * domain: 'pizzapalace.com',
725
+ * trigger: { type: 'subscriber_created' }
726
+ * });
727
+ *
728
+ * // Create workflow (single brand)
729
+ * const result = await client.workflows.create({
730
+ * name: 'Onboarding Flow',
731
+ * trigger: { type: 'api' }
732
+ * });
733
+ * ```
734
+ */
735
+ async create(params) {
736
+ return this.request("/workflows", {
737
+ method: "POST",
738
+ body: JSON.stringify(params)
739
+ });
740
+ }
741
+ /**
742
+ * List all workflows
743
+ *
744
+ * @example
745
+ * ```ts
746
+ * // List all workflows
747
+ * const { workflows } = await client.workflows.list();
748
+ *
749
+ * // List workflows for a specific client (multi-brand)
750
+ * const { workflows } = await platform.workflows.list({ domain: 'pizzapalace.com' });
751
+ * ```
752
+ */
753
+ async list(params = {}) {
754
+ const searchParams = new URLSearchParams();
755
+ if (params.domain) searchParams.set("domain", params.domain);
756
+ if (params.workspace_id) searchParams.set("workspace_id", params.workspace_id);
757
+ const query = searchParams.toString();
758
+ const endpoint = query ? `/workflows?${query}` : "/workflows";
759
+ return this.request(endpoint, {
760
+ method: "GET"
761
+ });
762
+ }
763
+ /**
764
+ * Get a workflow by ID (includes nodes and edges)
765
+ *
766
+ * @example
767
+ * ```ts
768
+ * const { workflow } = await client.workflows.get('workflow-uuid');
769
+ * console.log(workflow.nodes); // The workflow steps
770
+ * console.log(workflow.edges); // Connections between steps
771
+ * ```
772
+ */
773
+ async get(workflowId) {
774
+ return this.request(`/workflows/${workflowId}`, {
775
+ method: "GET"
776
+ });
777
+ }
778
+ /**
779
+ * Update a workflow (name, nodes, edges, trigger)
780
+ *
781
+ * @example
782
+ * ```ts
783
+ * // Update workflow name
784
+ * await client.workflows.update('workflow-uuid', { name: 'New Name' });
785
+ *
786
+ * // Update workflow steps
787
+ * await client.workflows.update('workflow-uuid', {
788
+ * nodes: [
789
+ * { id: '1', type: 'trigger', data: { triggerType: 'api' } },
790
+ * { id: '2', type: 'email', data: { templateId: 'tmpl_xxx', subject: 'Welcome!' } },
791
+ * { id: '3', type: 'delay', data: { duration: 3, unit: 'days' } }
792
+ * ],
793
+ * edges: [
794
+ * { id: 'e1', source: '1', target: '2' },
795
+ * { id: 'e2', source: '2', target: '3' }
796
+ * ]
797
+ * });
798
+ * ```
799
+ */
800
+ async update(workflowId, params) {
801
+ return this.request(`/workflows/${workflowId}`, {
802
+ method: "PUT",
803
+ body: JSON.stringify(params)
804
+ });
805
+ }
806
+ /**
807
+ * Delete a workflow
808
+ */
809
+ async delete(workflowId) {
810
+ return this.request(`/workflows/${workflowId}`, {
811
+ method: "DELETE"
812
+ });
813
+ }
814
+ /**
815
+ * Activate a workflow (start accepting triggers)
816
+ *
817
+ * @example
818
+ * ```ts
819
+ * await client.workflows.activate('workflow-uuid');
820
+ * // Workflow is now active and will process triggers
821
+ * ```
822
+ */
823
+ async activate(workflowId) {
824
+ return this.request(`/workflows/${workflowId}/activate`, {
825
+ method: "POST"
826
+ });
827
+ }
828
+ /**
829
+ * Pause a workflow (stop accepting new triggers)
830
+ *
831
+ * @example
832
+ * ```ts
833
+ * await client.workflows.pause('workflow-uuid');
834
+ * // Workflow is paused - existing executions continue, but no new ones start
835
+ * ```
836
+ */
837
+ async pause(workflowId) {
838
+ return this.request(`/workflows/${workflowId}/pause`, {
839
+ method: "POST"
840
+ });
841
+ }
842
+ /**
843
+ * Trigger a workflow for a subscriber
844
+ *
845
+ * @example
846
+ * ```ts
847
+ * // Trigger when a customer signs up
848
+ * const result = await platform.workflows.trigger('workflow-uuid', {
849
+ * email: 'customer@example.com',
850
+ * data: {
851
+ * firstName: 'John',
852
+ * restaurantName: 'Pizza Palace',
853
+ * signupSource: 'website'
854
+ * }
855
+ * });
856
+ *
857
+ * console.log(result.execution_id); // Track this execution
858
+ * ```
859
+ */
860
+ async trigger(workflowId, params) {
861
+ return this.request(`/webhooks/${workflowId}`, {
862
+ method: "POST",
863
+ body: JSON.stringify(params)
864
+ });
865
+ }
866
+ /**
867
+ * Send a custom event to resume waiting workflows
868
+ *
869
+ * Use this when you have workflows waiting for specific events (e.g., "purchase_completed").
870
+ * All workflows waiting for this event for the given subscriber will resume.
871
+ *
872
+ * @example
873
+ * ```ts
874
+ * // When a customer completes a purchase
875
+ * await platform.workflows.sendEvent({
876
+ * event: 'purchase_completed',
877
+ * email: 'customer@example.com',
878
+ * data: { orderTotal: 45.99, orderId: 'ORD-123' }
879
+ * });
880
+ *
881
+ * // Any workflow with a "wait_for_event: purchase_completed" node will resume
882
+ * ```
883
+ */
884
+ async sendEvent(params) {
885
+ return this.request("/events", {
886
+ method: "POST",
887
+ body: JSON.stringify(params)
888
+ });
889
+ }
890
+ };
891
+
892
+ // src/resources/webhooks.ts
893
+ var WebhooksResource = class {
894
+ constructor(request) {
895
+ this.request = request;
896
+ }
897
+ /**
898
+ * Create a new webhook endpoint
899
+ *
900
+ * @example
901
+ * ```ts
902
+ * const webhook = await client.webhooks.create({
903
+ * url: 'https://yourserver.com/webhooks',
904
+ * events: ['email.sent', 'email.delivered', 'email.opened'],
905
+ * secret: 'your-secret-key' // Optional, auto-generated if not provided
906
+ * });
907
+ *
908
+ * console.log('Webhook secret:', webhook.secret);
909
+ * ```
910
+ */
911
+ async create(params) {
912
+ return this.request("/webhooks", {
913
+ method: "POST",
914
+ body: JSON.stringify(params)
915
+ });
916
+ }
917
+ /**
918
+ * List all webhooks
919
+ */
920
+ async list() {
921
+ return this.request("/webhooks", {
922
+ method: "GET"
923
+ });
924
+ }
925
+ /**
926
+ * Get a webhook by ID with recent delivery history
927
+ */
928
+ async get(webhookId) {
929
+ return this.request(`/webhooks/${webhookId}`, {
930
+ method: "GET"
931
+ });
932
+ }
933
+ /**
934
+ * Update a webhook
935
+ *
936
+ * @example
937
+ * ```ts
938
+ * // Update webhook URL
939
+ * await client.webhooks.update('webhook-id', {
940
+ * url: 'https://newserver.com/webhooks'
941
+ * });
942
+ *
943
+ * // Add new events
944
+ * await client.webhooks.update('webhook-id', {
945
+ * events: ['email.sent', 'email.delivered', 'email.bounced']
946
+ * });
947
+ *
948
+ * // Disable webhook
949
+ * await client.webhooks.update('webhook-id', { active: false });
950
+ * ```
951
+ */
952
+ async update(webhookId, params) {
953
+ return this.request(`/webhooks/${webhookId}`, {
954
+ method: "PATCH",
955
+ body: JSON.stringify(params)
956
+ });
957
+ }
958
+ /**
959
+ * Delete a webhook
960
+ */
961
+ async delete(webhookId) {
962
+ return this.request(`/webhooks/${webhookId}`, {
963
+ method: "DELETE"
964
+ });
965
+ }
966
+ /**
967
+ * Test a webhook by sending a test event
968
+ *
969
+ * @example
970
+ * ```ts
971
+ * const result = await client.webhooks.test('webhook-id');
972
+ * if (result.test.sent) {
973
+ * console.log(`Test delivered in ${result.test.responseTimeMs}ms`);
974
+ * } else {
975
+ * console.log('Test failed:', result.test.error);
976
+ * }
977
+ * ```
978
+ */
979
+ async test(webhookId) {
980
+ return this.request(`/webhooks/${webhookId}/test`, {
981
+ method: "POST"
982
+ });
983
+ }
984
+ /**
985
+ * Enable a webhook
986
+ */
987
+ async enable(webhookId) {
988
+ return this.update(webhookId, { active: true });
989
+ }
990
+ /**
991
+ * Disable a webhook
992
+ */
993
+ async disable(webhookId) {
994
+ return this.update(webhookId, { active: false });
995
+ }
555
996
  };
556
997
 
557
998
  // src/client.ts
@@ -584,6 +1025,8 @@ var SendMailOS = class {
584
1025
  this.domains = new DomainsResource(boundRequest);
585
1026
  this.organization = new OrganizationResource(boundRequest);
586
1027
  this.workspaces = new WorkspacesResource(boundRequest);
1028
+ this.workflows = new WorkflowsResource(boundRequest);
1029
+ this.webhooks = new WebhooksResource(boundRequest);
587
1030
  }
588
1031
  /**
589
1032
  * Make an authenticated request to the API