@christian-ek/sweego 0.1.0

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 (72) hide show
  1. package/README.md +357 -0
  2. package/dist/client/_generated/_ignore.d.ts +1 -0
  3. package/dist/client/_generated/_ignore.d.ts.map +1 -0
  4. package/dist/client/_generated/_ignore.js +3 -0
  5. package/dist/client/_generated/_ignore.js.map +1 -0
  6. package/dist/client/index.d.ts +282 -0
  7. package/dist/client/index.d.ts.map +1 -0
  8. package/dist/client/index.js +265 -0
  9. package/dist/client/index.js.map +1 -0
  10. package/dist/client/webhook.d.ts +42 -0
  11. package/dist/client/webhook.d.ts.map +1 -0
  12. package/dist/client/webhook.js +89 -0
  13. package/dist/client/webhook.js.map +1 -0
  14. package/dist/component/_generated/api.d.ts +43 -0
  15. package/dist/component/_generated/api.d.ts.map +1 -0
  16. package/dist/component/_generated/api.js +31 -0
  17. package/dist/component/_generated/api.js.map +1 -0
  18. package/dist/component/_generated/component.d.ts +226 -0
  19. package/dist/component/_generated/component.d.ts.map +1 -0
  20. package/dist/component/_generated/component.js +11 -0
  21. package/dist/component/_generated/component.js.map +1 -0
  22. package/dist/component/_generated/dataModel.d.ts +46 -0
  23. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  24. package/dist/component/_generated/dataModel.js +11 -0
  25. package/dist/component/_generated/dataModel.js.map +1 -0
  26. package/dist/component/_generated/server.d.ts +121 -0
  27. package/dist/component/_generated/server.d.ts.map +1 -0
  28. package/dist/component/_generated/server.js +78 -0
  29. package/dist/component/_generated/server.js.map +1 -0
  30. package/dist/component/convex.config.d.ts +3 -0
  31. package/dist/component/convex.config.d.ts.map +1 -0
  32. package/dist/component/convex.config.js +10 -0
  33. package/dist/component/convex.config.js.map +1 -0
  34. package/dist/component/lib.d.ts +319 -0
  35. package/dist/component/lib.d.ts.map +1 -0
  36. package/dist/component/lib.js +725 -0
  37. package/dist/component/lib.js.map +1 -0
  38. package/dist/component/schema.d.ts +259 -0
  39. package/dist/component/schema.d.ts.map +1 -0
  40. package/dist/component/schema.js +99 -0
  41. package/dist/component/schema.js.map +1 -0
  42. package/dist/component/shared.d.ts +280 -0
  43. package/dist/component/shared.d.ts.map +1 -0
  44. package/dist/component/shared.js +213 -0
  45. package/dist/component/shared.js.map +1 -0
  46. package/dist/component/sweego.d.ts +95 -0
  47. package/dist/component/sweego.d.ts.map +1 -0
  48. package/dist/component/sweego.js +210 -0
  49. package/dist/component/sweego.js.map +1 -0
  50. package/dist/component/utils.d.ts +16 -0
  51. package/dist/component/utils.d.ts.map +1 -0
  52. package/dist/component/utils.js +29 -0
  53. package/dist/component/utils.js.map +1 -0
  54. package/package.json +100 -0
  55. package/src/client/_generated/_ignore.ts +1 -0
  56. package/src/client/index.ts +490 -0
  57. package/src/client/webhook.test.ts +146 -0
  58. package/src/client/webhook.ts +130 -0
  59. package/src/component/_generated/api.ts +59 -0
  60. package/src/component/_generated/component.ts +244 -0
  61. package/src/component/_generated/dataModel.ts +60 -0
  62. package/src/component/_generated/server.ts +156 -0
  63. package/src/component/convex.config.ts +12 -0
  64. package/src/component/lib.test.ts +189 -0
  65. package/src/component/lib.ts +835 -0
  66. package/src/component/schema.ts +117 -0
  67. package/src/component/shared.test.ts +64 -0
  68. package/src/component/shared.ts +315 -0
  69. package/src/component/sweego.test.ts +141 -0
  70. package/src/component/sweego.ts +310 -0
  71. package/src/component/utils.ts +35 -0
  72. package/src/test.ts +20 -0
package/README.md ADDED
@@ -0,0 +1,357 @@
1
+ # Sweego Convex Component
2
+
3
+ [![npm version](https://badge.fury.io/js/@christian-ek%2Fsweego.svg)](https://www.npmjs.com/package/@christian-ek/sweego)
4
+
5
+ A [Convex component](https://www.convex.dev/components) for sending
6
+ **transactional email and SMS** through [Sweego](https://www.sweego.io), with
7
+ durable delivery and webhook-based delivery tracking.
8
+
9
+ Features:
10
+
11
+ - **Email + SMS** through Sweego's unified `/send` API, plus personalized
12
+ **bulk email** (`/send/bulk/email`).
13
+ - **Durable execution** — sends run in a [workpool](https://www.convex.dev/components/workpool)
14
+ and are retried automatically through transient failures (429s, 5xx, network
15
+ blips). Permanent failures (4xx) are recorded without futile retries.
16
+ - **Delivery tracking** — verifies Sweego's HMAC-SHA256 webhook signatures and
17
+ maintains per-recipient delivery state (delivered / bounced / opened /
18
+ clicked / complained / unsubscribed; SMS undelivered / stopped / clicked).
19
+ - **Templates, attachments, custom headers, List-Unsubscribe, expiry, and
20
+ campaign metadata** — the full send surface.
21
+ - **Event callbacks** — register a mutation that runs whenever a delivery event
22
+ arrives.
23
+ - **Status, cancellation, and retention cleanup** out of the box.
24
+
25
+ No `svix` dependency and no Sweego SDK — the component calls the REST API with
26
+ `fetch` and verifies signatures with the Web Crypto API.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @christian-ek/sweego
32
+ ```
33
+
34
+ Create a [Sweego](https://app.sweego.io) account, verify a sending domain (and
35
+ set up an SMS channel if you want SMS), and create an API key. Set it in your
36
+ Convex deployment:
37
+
38
+ ```bash
39
+ npx convex env set SWEEGO_API_KEY swg_xxxxxxxx
40
+ ```
41
+
42
+ Add the component to your app in `convex/convex.config.ts`:
43
+
44
+ ```ts
45
+ import { defineApp } from "convex/server";
46
+ import sweego from "@christian-ek/sweego/convex.config";
47
+
48
+ const app = defineApp();
49
+ app.use(sweego);
50
+
51
+ export default app;
52
+ ```
53
+
54
+ ## Get started
55
+
56
+ ```ts
57
+ // convex/sweego.ts
58
+ import { components } from "./_generated/api";
59
+ import { Sweego } from "@christian-ek/sweego";
60
+ import { internalMutation } from "./_generated/server";
61
+
62
+ export const sweego = new Sweego(components.sweego, {});
63
+
64
+ export const sendWelcome = internalMutation({
65
+ handler: async (ctx) => {
66
+ await sweego.sendEmail(ctx, {
67
+ from: "Acme <hello@yourdomain.com>",
68
+ to: "user@example.com",
69
+ subject: "Welcome!",
70
+ text: "Welcome to Acme!", // Sweego requires text (or a template)
71
+ html: "<h1>Welcome to Acme</h1>", // html is supplementary
72
+ });
73
+ },
74
+ });
75
+ ```
76
+
77
+ `sendEmail` (and `sendSms` / `sendBulkEmail`) can be called from a **mutation or
78
+ an action**. It enqueues the message and returns a `MessageId` immediately; the
79
+ component delivers it durably in the background.
80
+
81
+ `from`/`to`/`cc`/`bcc`/`replyTo` accept either `"Name <email>"` strings or
82
+ `{ email, name }` objects.
83
+
84
+ ## Sending SMS
85
+
86
+ SMS goes through the same component. Sweego requires a `campaignType`
87
+ (`"transac"` or `"market"`), and a region for each recipient:
88
+
89
+ ```ts
90
+ await sweego.sendSms(ctx, {
91
+ to: "+33600000000",
92
+ region: "FR", // ISO-3166 alpha-2; applied to bare-string recipients
93
+ campaignType: "transac",
94
+ senderId: "Acme", // 3–11 chars; required if you have multiple sender IDs
95
+ text: "Your code is 123456",
96
+ });
97
+ ```
98
+
99
+ You can also pass structured recipients: `to: [{ num: "+1...", region: "US" }]`.
100
+
101
+ Estimate cost/segments before sending (from an action):
102
+
103
+ ```ts
104
+ const estimate = await sweego.estimateSms(ctx, {
105
+ campaign_type: "transac",
106
+ message_txt: "Your code is 123456",
107
+ recipients: [{ num: "+33600000000", region: "FR" }],
108
+ });
109
+ ```
110
+
111
+ ## Templates
112
+
113
+ Reference a Sweego-hosted template by id and pass `variables` for
114
+ `{{ placeholder }}` interpolation:
115
+
116
+ ```ts
117
+ await sweego.sendEmail(ctx, {
118
+ from: "Acme <hello@yourdomain.com>",
119
+ to: "user@example.com",
120
+ subject: "Your receipt",
121
+ templateId: "your-template-uuid",
122
+ variables: { name: "Ada", amount: 42 },
123
+ });
124
+ ```
125
+
126
+ > You cannot combine `templateId` with `html`. On a single `/send`, `variables`
127
+ > are only applied for a single recipient — use `sendBulkEmail` for
128
+ > per-recipient personalization.
129
+
130
+ ## Bulk personalized email
131
+
132
+ ```ts
133
+ await sweego.sendBulkEmail(ctx, {
134
+ from: "Acme <hello@yourdomain.com>",
135
+ subject: "Newsletter",
136
+ templateId: "your-template-uuid",
137
+ recipients: [
138
+ { email: "alice@example.com", variables: { name: "Alice" } },
139
+ { email: "bob@example.com", variables: { name: "Bob" } },
140
+ ],
141
+ });
142
+ ```
143
+
144
+ Bulk sends require ≥2 recipients and do not support `cc`/`bcc`/`replyTo`.
145
+
146
+ ## Attachments, headers, and more
147
+
148
+ ```ts
149
+ await sweego.sendEmail(ctx, {
150
+ from: "Acme <hello@yourdomain.com>",
151
+ to: "user@example.com",
152
+ subject: "Your invoice",
153
+ text: "Your invoice is attached.",
154
+ html: "<p>See attached.</p>",
155
+ attachments: [
156
+ { filename: "invoice.pdf", content: base64Pdf /* base64-encoded bytes */ },
157
+ ],
158
+ headers: { "Ref-1": "643524" }, // omit the X- prefix; max 5 headers
159
+ listUnsub: { method: "one-click", value: "<mailto:unsub@acme.com>,<https://acme.com/u>" },
160
+ expires: "2026-07-26T19:30:00+02:00", // or a delta like "1 day"
161
+ campaignType: "transac",
162
+ campaignTags: ["welcome"],
163
+ });
164
+ ```
165
+
166
+ > **Attachment size:** attachment bytes are stored inline with the message, so
167
+ > the whole message must fit within Convex's ~1 MiB document limit. Keep
168
+ > attachments small; host large files elsewhere and link to them.
169
+
170
+ ## Tracking status
171
+
172
+ `sendEmail`/`sendSms` return a branded `MessageId`. Use it to:
173
+
174
+ ```ts
175
+ const status = await sweego.status(ctx, messageId);
176
+ // {
177
+ // status: "queued" | "sent" | "failed" | "cancelled",
178
+ // channel, transactionId, errorMessage, creditLeft,
179
+ // deliveries: [
180
+ // { swgUid, recipientKey, status, delivered, bounced, opened, clicked, ... }
181
+ // ],
182
+ // }
183
+
184
+ const full = await sweego.get(ctx, messageId); // full message + deliveries
185
+ const cancelled = await sweego.cancel(ctx, messageId); // true if not yet sent
186
+ ```
187
+
188
+ A message produces one **delivery** per recipient (Sweego returns one `swg_uid`
189
+ per recipient), each tracked independently.
190
+
191
+ ## Webhooks (delivery events)
192
+
193
+ Sending alone won't tell you whether a message was delivered, bounced, opened,
194
+ or clicked — for that, set up a webhook.
195
+
196
+ 1. Mount an HTTP route in `convex/http.ts`:
197
+
198
+ ```ts
199
+ import { httpRouter } from "convex/server";
200
+ import { httpAction } from "./_generated/server";
201
+ import { sweego } from "./sweego";
202
+
203
+ const http = httpRouter();
204
+ http.route({
205
+ path: "/sweego-webhook",
206
+ method: "POST",
207
+ handler: httpAction(async (ctx, req) => sweego.handleSweegoWebhook(ctx, req)),
208
+ });
209
+ export default http;
210
+ ```
211
+
212
+ Your endpoint is then `https://<your-deployment>.convex.site/sweego-webhook`.
213
+
214
+ 2. In the Sweego dashboard, create a webhook pointing at that URL and select the
215
+ email/SMS events you care about.
216
+
217
+ 3. Copy the webhook's **signing secret** and set it in your deployment:
218
+
219
+ ```bash
220
+ npx convex env set SWEEGO_WEBHOOK_SECRET <secret>
221
+ ```
222
+
223
+ The component verifies Sweego's `webhook-id` / `webhook-timestamp` /
224
+ `webhook-signature` HMAC-SHA256 signature against the raw request body before
225
+ processing anything. Set `webhookToleranceSeconds` in the options to also reject
226
+ stale (replayed) requests.
227
+
228
+ ### Reacting to events
229
+
230
+ Register an `onEvent` mutation that runs whenever a delivery event arrives:
231
+
232
+ ```ts
233
+ import { components } from "./_generated/api";
234
+ import { internal } from "./_generated/api";
235
+ import { Sweego, vOnEventArgs } from "@christian-ek/sweego";
236
+ import { internalMutation } from "./_generated/server";
237
+
238
+ export const sweego = new Sweego(components.sweego, {
239
+ onEvent: internal.sweego.handleEvent,
240
+ });
241
+
242
+ export const handleEvent = internalMutation({
243
+ args: vOnEventArgs, // { messageId, swgUid, event }
244
+ handler: async (ctx, { messageId, swgUid, event }) => {
245
+ // event.eventType e.g. "delivered", "hard_bounce", "email_opened",
246
+ // "sms_undelivered", ...; event.raw holds the full Sweego payload.
247
+ console.log(messageId, swgUid, event.eventType);
248
+ },
249
+ });
250
+ ```
251
+
252
+ > With `vOnEventArgs`, `event` is loosely typed. For a fully-typed `event`
253
+ > (`SweegoEvent`), define the handler with `sweego.defineEventHandler(async (ctx, { messageId, swgUid, event }) => { ... })`
254
+ > instead — it registers an internal mutation with the right argument types.
255
+
256
+ > No webhooks yet? `sweego.refreshStatus(ctx, messageId)` (from an action) polls
257
+ > Sweego's logs and updates delivery state on demand.
258
+
259
+ ## Options
260
+
261
+ ```ts
262
+ new Sweego(components.sweego, {
263
+ apiKey, // default: process.env.SWEEGO_API_KEY
264
+ webhookSecret, // default: process.env.SWEEGO_WEBHOOK_SECRET
265
+ provider, // default: "sweego"
266
+ testMode, // default: false — see below
267
+ initialBackoffMs, // default: 30000
268
+ retryAttempts, // default: 5
269
+ webhookToleranceSeconds, // default: 0 (disabled)
270
+ onEvent, // your event-handler mutation reference
271
+ });
272
+ ```
273
+
274
+ **`testMode`** — when `true`, email sends are submitted with Sweego's `dry-run`
275
+ (validated by Sweego but never delivered) and SMS sends use BAT test mode. It is
276
+ **off by default**: because `dry-run` produces no real delivery and no webhooks,
277
+ you must opt in. You can also set `dryRun: true` (email) or `bat: true` (SMS) on
278
+ an individual send.
279
+
280
+ ## Data retention
281
+
282
+ The component retains messages, deliveries, and raw events. Clean them up on a
283
+ schedule with the built-in mutations:
284
+
285
+ ```ts
286
+ // convex/crons.ts
287
+ import { cronJobs } from "convex/server";
288
+ import { components, internal } from "./_generated/api";
289
+ import { internalMutation } from "./_generated/server";
290
+ import { v } from "convex/values";
291
+
292
+ const crons = cronJobs();
293
+ crons.interval(
294
+ "Clean up old Sweego messages",
295
+ { hours: 1 },
296
+ internal.crons.cleanupSweego,
297
+ );
298
+
299
+ const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000;
300
+ export const cleanupSweego = internalMutation({
301
+ args: {},
302
+ returns: v.null(),
303
+ handler: async (ctx) => {
304
+ await ctx.scheduler.runAfter(0, components.sweego.lib.cleanupOldMessages, {
305
+ olderThan: ONE_WEEK_MS,
306
+ });
307
+ await ctx.scheduler.runAfter(
308
+ 0,
309
+ components.sweego.lib.cleanupAbandonedMessages,
310
+ { olderThan: 4 * ONE_WEEK_MS },
311
+ );
312
+ },
313
+ });
314
+
315
+ export default crons;
316
+ ```
317
+
318
+ `cleanupOldMessages` deletes finalized messages (and their deliveries/events)
319
+ older than the cutoff (default 7 days); `cleanupAbandonedMessages` clears
320
+ never-finalized messages (default 30 days).
321
+
322
+ ## Notes & gotchas
323
+
324
+ - **Email body:** Sweego requires **`text`** (the plain-text part) *or* a
325
+ **`templateId`**. `html` is supplementary — sending `html` **alone** is
326
+ rejected with `Either 'message-txt' or 'template-id' is required`. The
327
+ component enforces this locally (it throws before sending), so you get a clear
328
+ error instead of a 422. Always include `text` alongside `html`.
329
+ - **Auth:** Sweego authenticates with an `Api-Key:` header (not
330
+ `Authorization: Bearer`) — handled for you.
331
+ - **Field names:** Sweego's API uses hyphenated keys (`message-html`,
332
+ `template-id`, `campaign-type`, …). The component maps your camelCase options
333
+ to the exact wire format.
334
+ - **SMS:** there's no bulk SMS endpoint — multi-recipient SMS goes through one
335
+ `/send` call. `campaignType` is required, and each recipient's `region` must
336
+ match the number's country (e.g. `+46…` → `SE`) or Sweego 422s. SMS
337
+ `variables` are shared across all recipients.
338
+ - **Senders must be authorized by Sweego (account-level):** the email `from`
339
+ must be on a domain verified for your API key, and SMS sender IDs must be
340
+ registered — some regions (e.g. Sweden) *require* a verified alphanumeric
341
+ sender ID and reject the default numeric sender, and US/Canada require a
342
+ Toll-Free Number passed as `senderId`. These are dashboard settings; the
343
+ component surfaces Sweego's rejection but can't pre-validate them.
344
+ - **Open/click tracking** is enabled per-domain in the Sweego dashboard, not per
345
+ send. Tracking events can be delayed up to ~10 minutes.
346
+ - **Rate limiting:** Sweego does not publish API limits, so the component bounds
347
+ throughput via the send workpool's parallelism and retries 429s with backoff
348
+ rather than guessing a fixed rate.
349
+ - **Delivery is at-least-once.** Sweego's `/send` exposes no idempotency key, so
350
+ the component never retries once a request has been accepted (only transient
351
+ failures *before* acceptance are retried). In the rare event the process dies
352
+ between Sweego accepting a request and the component recording it, a retry
353
+ could re-send. This window is kept as small as possible.
354
+
355
+ ## License
356
+
357
+ Apache-2.0
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=_ignore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_ignore.d.ts","sourceRoot":"","sources":["../../../src/client/_generated/_ignore.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // This is only here so convex-test can detect a _generated folder
3
+ //# sourceMappingURL=_ignore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_ignore.js","sourceRoot":"","sources":["../../../src/client/_generated/_ignore.ts"],"names":[],"mappings":";AAAA,kEAAkE"}
@@ -0,0 +1,282 @@
1
+ import { type FunctionReference, type FunctionVisibility, type GenericDataModel, type GenericMutationCtx } from "convex/server";
2
+ import { type VString } from "convex/values";
3
+ import type { ComponentApi } from "../component/_generated/component.js";
4
+ import { type ActionCtx, type Attachment, type EmailAddress, type ListUnsub, type MutationCtx, type QueryCtx, type SmsCampaignType, type SmsRecipient, type SweegoEvent, type Variables } from "../component/shared.js";
5
+ export type SweegoComponent = ComponentApi;
6
+ export type MessageId = string & {
7
+ __isSweegoMessageId: true;
8
+ };
9
+ export declare const vMessageId: VString<MessageId>;
10
+ export { vSweegoEvent } from "../component/shared.js";
11
+ export type { Attachment, Channel, DeliveryStatus, EmailAddress, ListUnsub, SendStatus, SmsCampaignType, SmsRecipient, SweegoEvent, Variables, } from "../component/shared.js";
12
+ export declare const vOnEventArgs: {
13
+ messageId: VString<MessageId, "required">;
14
+ swgUid: VString<string, "required">;
15
+ event: import("convex/values").VAny<any, "required", string>;
16
+ };
17
+ type EventHandlerRef = FunctionReference<"mutation", FunctionVisibility, {
18
+ messageId: MessageId;
19
+ swgUid: string;
20
+ event: SweegoEvent;
21
+ }>;
22
+ export type SweegoOptions = {
23
+ /** Sweego API key. Defaults to `process.env.SWEEGO_API_KEY`. */
24
+ apiKey?: string;
25
+ /**
26
+ * Webhook signing secret (from the Sweego dashboard). Defaults to
27
+ * `process.env.SWEEGO_WEBHOOK_SECRET`. Required only to verify webhooks.
28
+ */
29
+ webhookSecret?: string;
30
+ /** The Sweego provider. Defaults to "sweego". */
31
+ provider?: string;
32
+ /**
33
+ * When true, email sends are submitted with `dry-run` (Sweego validates but
34
+ * does not send) and SMS sends use BAT test mode. Defaults to false.
35
+ */
36
+ testMode?: boolean;
37
+ /** Initial retry backoff in ms for the send workpool. Defaults to 30000. */
38
+ initialBackoffMs?: number;
39
+ /** Max send attempts before giving up. Defaults to 5. */
40
+ retryAttempts?: number;
41
+ /**
42
+ * Reject webhooks whose timestamp differs from now by more than this many
43
+ * seconds (replay protection). Defaults to 300 (5 minutes), per the Standard
44
+ * Webhooks recommendation. Set to 0 to disable the timestamp check (not
45
+ * recommended — webhook-id deduplication still applies either way).
46
+ */
47
+ webhookToleranceSeconds?: number;
48
+ /** A mutation in your app to run after each delivery event. */
49
+ onEvent?: EventHandlerRef | null;
50
+ };
51
+ type ResolvedConfig = {
52
+ provider: string;
53
+ testMode: boolean;
54
+ initialBackoffMs: number;
55
+ retryAttempts: number;
56
+ webhookToleranceSeconds: number;
57
+ };
58
+ type AddressInput = string | EmailAddress;
59
+ type CommonEmailOptions = {
60
+ /** Plain-text body. Required unless `templateId` is set. */
61
+ text?: string;
62
+ /**
63
+ * Supplementary HTML body. Sweego rejects an email sent with `html` but no
64
+ * `text`/`templateId` — always pair `html` with `text` (or use a template).
65
+ */
66
+ html?: string;
67
+ templateId?: string;
68
+ attachments?: Attachment[];
69
+ headers?: Record<string, string>;
70
+ listUnsub?: ListUnsub;
71
+ expires?: string;
72
+ campaignId?: string;
73
+ campaignTags?: string[];
74
+ campaignType?: "market" | "newsletter" | "transac";
75
+ compressStyle?: boolean;
76
+ forceInlineStyle?: boolean;
77
+ dryRun?: boolean;
78
+ };
79
+ export type SendEmailOptions = CommonEmailOptions & {
80
+ from: AddressInput;
81
+ to: AddressInput | AddressInput[];
82
+ cc?: AddressInput | AddressInput[];
83
+ bcc?: AddressInput | AddressInput[];
84
+ replyTo?: AddressInput;
85
+ subject?: string;
86
+ /** Template variables. Only applied for a single recipient on /send. */
87
+ variables?: Variables;
88
+ };
89
+ export type BulkEmailRecipient = {
90
+ email: string;
91
+ name?: string;
92
+ variables?: Variables;
93
+ };
94
+ export type SendBulkEmailOptions = CommonEmailOptions & {
95
+ from: AddressInput;
96
+ subject?: string;
97
+ /** At least 2 recipients; each may carry its own `variables`. */
98
+ recipients: Array<BulkEmailRecipient | string>;
99
+ };
100
+ export type SendSmsOptions = {
101
+ to: SmsRecipient | SmsRecipient[] | string | string[];
102
+ /** Default ISO region (e.g. "FR") for recipients given as bare strings. */
103
+ region?: string;
104
+ text?: string;
105
+ templateId?: string;
106
+ variables?: Variables;
107
+ /** REQUIRED by Sweego for SMS. */
108
+ campaignType: SmsCampaignType;
109
+ senderId?: string;
110
+ shortenUrls?: boolean;
111
+ shortenWithProtocol?: boolean;
112
+ /** BAT test mode. */
113
+ bat?: boolean;
114
+ campaignId?: string;
115
+ };
116
+ export declare class Sweego {
117
+ component: SweegoComponent;
118
+ readonly config: ResolvedConfig;
119
+ private readonly _apiKey?;
120
+ private readonly _webhookSecret?;
121
+ readonly onEvent?: EventHandlerRef | null;
122
+ /**
123
+ * @param component The mounted component, e.g. `components.sweego`.
124
+ * @param options {@link SweegoOptions}.
125
+ */
126
+ constructor(component: SweegoComponent, options?: SweegoOptions);
127
+ private get apiKey();
128
+ private get webhookSecret();
129
+ private runtimeConfig;
130
+ /**
131
+ * Enqueue an email. It is sent durably (with retries) by the component's
132
+ * workpool. Returns the {@link MessageId} you can use to check status,
133
+ * cancel, or correlate webhook events.
134
+ */
135
+ sendEmail(ctx: MutationCtx | ActionCtx, options: SendEmailOptions): Promise<MessageId>;
136
+ /**
137
+ * Enqueue a personalized bulk email (Sweego's `/send/bulk/email`). Each
138
+ * recipient may carry its own `variables`. No cc/bcc/replyTo support.
139
+ */
140
+ sendBulkEmail(ctx: MutationCtx | ActionCtx, options: SendBulkEmailOptions): Promise<MessageId>;
141
+ /** Enqueue an SMS (Sweego's `/send` with `channel: "sms"`). */
142
+ sendSms(ctx: MutationCtx | ActionCtx, options: SendSmsOptions): Promise<MessageId>;
143
+ private enqueue;
144
+ /** Aggregate status of a message plus per-recipient delivery state. */
145
+ status(ctx: QueryCtx | MutationCtx | ActionCtx, messageId: MessageId): Promise<{
146
+ channel: "email" | "sms";
147
+ creditLeft: string | null;
148
+ deliveries: Array<{
149
+ bounced: boolean;
150
+ channel: "email" | "sms";
151
+ clicked: boolean;
152
+ complained: boolean;
153
+ delivered: boolean;
154
+ errorMessage: string | null;
155
+ lastEventType: string | null;
156
+ opened: boolean;
157
+ recipientKey: string;
158
+ softBounced: boolean;
159
+ status: "pending" | "sent" | "delivered" | "soft_bounced" | "bounced" | "undelivered" | "stopped";
160
+ stopped: boolean;
161
+ swgUid: string;
162
+ unsubscribed: boolean;
163
+ }>;
164
+ errorMessage: string | null;
165
+ status: "queued" | "sent" | "failed" | "cancelled";
166
+ transactionId: string | null;
167
+ } | null>;
168
+ /** The full stored message plus its deliveries. */
169
+ get(ctx: QueryCtx | MutationCtx | ActionCtx, messageId: MessageId): Promise<{
170
+ _creationTime: number;
171
+ _id: string;
172
+ attachments?: Array<{
173
+ content: string;
174
+ contentId?: string;
175
+ disposition?: "attachment" | "inline";
176
+ filename: string;
177
+ isRelated?: boolean;
178
+ }>;
179
+ bat?: boolean;
180
+ bcc?: Array<{
181
+ email: string;
182
+ name?: string;
183
+ }>;
184
+ bulk: boolean;
185
+ campaignId?: string;
186
+ campaignTags?: Array<string>;
187
+ campaignType?: string;
188
+ cc?: Array<{
189
+ email: string;
190
+ name?: string;
191
+ }>;
192
+ channel: "email" | "sms";
193
+ compressStyle?: boolean;
194
+ creditLeft?: string;
195
+ deliveries: Array<{
196
+ bounced: boolean;
197
+ channel: "email" | "sms";
198
+ clicked: boolean;
199
+ complained: boolean;
200
+ delivered: boolean;
201
+ errorMessage: string | null;
202
+ lastEventType: string | null;
203
+ opened: boolean;
204
+ recipientKey: string;
205
+ softBounced: boolean;
206
+ status: "pending" | "sent" | "delivered" | "soft_bounced" | "bounced" | "undelivered" | "stopped";
207
+ stopped: boolean;
208
+ swgUid: string;
209
+ unsubscribed: boolean;
210
+ }>;
211
+ dryRun?: boolean;
212
+ emailRecipients?: Array<{
213
+ email: string;
214
+ name?: string;
215
+ variables?: Record<string, string | number | boolean>;
216
+ }>;
217
+ errorMessage?: string;
218
+ expires?: string;
219
+ finalizedAt: number;
220
+ forceInlineStyle?: boolean;
221
+ from?: {
222
+ email: string;
223
+ name?: string;
224
+ };
225
+ headers?: Record<string, string>;
226
+ html?: string;
227
+ listUnsub?: {
228
+ method?: "mailto" | "one-click";
229
+ value: string;
230
+ };
231
+ provider: string;
232
+ replyTo?: {
233
+ email: string;
234
+ name?: string;
235
+ };
236
+ senderId?: string;
237
+ shortenUrls?: boolean;
238
+ shortenWithProtocol?: boolean;
239
+ smsRecipients?: Array<{
240
+ num: string;
241
+ region: string;
242
+ }>;
243
+ status: "queued" | "sent" | "failed" | "cancelled";
244
+ subject?: string;
245
+ templateId?: string;
246
+ text?: string;
247
+ transactionId?: string;
248
+ variables?: Record<string, string | number | boolean>;
249
+ } | null>;
250
+ /**
251
+ * Cancel a message if it has not yet been handed to Sweego. Returns true if
252
+ * it was cancelled, false if it had already been sent.
253
+ */
254
+ cancel(ctx: MutationCtx | ActionCtx, messageId: MessageId): Promise<boolean>;
255
+ /** Estimate the cost/segments of an SMS send (Sweego `/sms/estimate`). */
256
+ estimateSms(ctx: ActionCtx, body: Record<string, unknown>): Promise<unknown>;
257
+ /**
258
+ * Poll Sweego's logs to refresh delivery status without webhooks. Returns the
259
+ * number of deliveries updated. Prefer webhooks where possible.
260
+ */
261
+ refreshStatus(ctx: ActionCtx, messageId: MessageId): Promise<number>;
262
+ /**
263
+ * Verify and handle a Sweego webhook. Mount this on an HTTP route. Verifies
264
+ * the HMAC signature against the raw body, then updates delivery state and
265
+ * dispatches your `onEvent` handler.
266
+ */
267
+ handleSweegoWebhook(ctx: MutationCtx | ActionCtx, request: Request): Promise<Response>;
268
+ /**
269
+ * Helper to define your `onEvent` mutation with the right argument validator.
270
+ * Equivalent to declaring an `internalMutation` with {@link vOnEventArgs}.
271
+ */
272
+ defineEventHandler<DataModel extends GenericDataModel>(handler: (ctx: GenericMutationCtx<DataModel>, args: {
273
+ messageId: MessageId;
274
+ swgUid: string;
275
+ event: SweegoEvent;
276
+ }) => Promise<void>): import("convex/server").RegisteredMutation<"internal", {
277
+ messageId: MessageId;
278
+ swgUid: string;
279
+ event: SweegoEvent;
280
+ }, Promise<void>>;
281
+ }
282
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"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"}