@robbeverhelst/do 1.285.0 → 1.286.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 (2) hide show
  1. package/dist/do.js +271 -1
  2. package/package.json +1 -1
package/dist/do.js CHANGED
@@ -5096,6 +5096,67 @@ var seatingProposals = pgTable("seating_proposals", {
5096
5096
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
5097
5097
  }, (t) => [index("seating_proposals_list_idx").on(t.guestListId)]);
5098
5098
 
5099
+ // ../../packages/schema/src/db/mail.ts
5100
+ var mailCampaignStatus = pgEnum("mail_campaign_status", [
5101
+ "draft",
5102
+ "sending",
5103
+ "sent",
5104
+ "cancelled"
5105
+ ]);
5106
+ var mailDeliveryStatus = pgEnum("mail_delivery_status", [
5107
+ "queued",
5108
+ "sending",
5109
+ "sent",
5110
+ "delivered",
5111
+ "bounced",
5112
+ "failed",
5113
+ "skipped"
5114
+ ]);
5115
+ var mailCampaigns = pgTable("mail_campaigns", {
5116
+ id: text("id").primaryKey(),
5117
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
5118
+ guestListId: text("guest_list_id").references(() => guestLists.id, { onDelete: "cascade" }),
5119
+ kind: text("kind").notNull().default("message"),
5120
+ template: text("template").notNull(),
5121
+ data: jsonb("data").$type().notNull().default({}),
5122
+ replyTo: text("reply_to"),
5123
+ status: mailCampaignStatus("status").notNull().default("draft"),
5124
+ createdByUserId: text("created_by_user_id").references(() => users.id),
5125
+ testSentAt: timestamp("test_sent_at", { withTimezone: true }),
5126
+ startedAt: timestamp("started_at", { withTimezone: true }),
5127
+ finishedAt: timestamp("finished_at", { withTimezone: true }),
5128
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
5129
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
5130
+ }, (t) => [
5131
+ index("mail_campaigns_tenant_idx").on(t.tenantId),
5132
+ index("mail_campaigns_status_idx").on(t.status)
5133
+ ]);
5134
+ var mailDeliveries = pgTable("mail_deliveries", {
5135
+ id: text("id").primaryKey(),
5136
+ tenantId: text("tenant_id").notNull().references(() => tenants.id),
5137
+ campaignId: text("campaign_id").notNull().references(() => mailCampaigns.id, { onDelete: "cascade" }),
5138
+ contactId: text("contact_id").references(() => contacts.id),
5139
+ householdId: text("household_id").references(() => households.id),
5140
+ toEmail: text("to_email").notNull(),
5141
+ language: text("language").notNull().default("nl"),
5142
+ data: jsonb("data").$type().notNull().default({}),
5143
+ status: mailDeliveryStatus("status").notNull().default("queued"),
5144
+ transport: text("transport"),
5145
+ providerMessageId: text("provider_message_id"),
5146
+ attempts: integer("attempts").notNull().default(0),
5147
+ error: text("error"),
5148
+ sentAt: timestamp("sent_at", { withTimezone: true }),
5149
+ deliveredAt: timestamp("delivered_at", { withTimezone: true }),
5150
+ bouncedAt: timestamp("bounced_at", { withTimezone: true }),
5151
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
5152
+ }, (t) => [
5153
+ index("mail_deliveries_campaign_status_idx").on(t.campaignId, t.status),
5154
+ index("mail_deliveries_sent_at_idx").on(t.sentAt),
5155
+ index("mail_deliveries_household_idx").on(t.householdId),
5156
+ uniqueIndex("mail_deliveries_provider_message_uniq").on(t.providerMessageId),
5157
+ uniqueIndex("mail_deliveries_campaign_email_uniq").on(t.campaignId, t.toEmail)
5158
+ ]);
5159
+
5099
5160
  // ../../packages/schema/src/db/calendar.ts
5100
5161
  var calendarLinkPurpose = pgEnum("calendar_link_purpose", ["block", "deadline"]);
5101
5162
  var calendarEventLinks = pgTable("calendar_event_links", {
@@ -26739,6 +26800,124 @@ var thankYouViewSchema = object({
26739
26800
  thanked: array(thankYouGiftSchema)
26740
26801
  });
26741
26802
 
26803
+ // ../../packages/schema/src/contracts/mail.ts
26804
+ var MAIL_LANGUAGES = ["nl", "pt"];
26805
+ var mailLanguageSchema = _enum2(MAIL_LANGUAGES);
26806
+ var MAIL_CAMPAIGN_STATUSES = ["draft", "sending", "sent", "cancelled"];
26807
+ var mailCampaignStatusSchema = _enum2(MAIL_CAMPAIGN_STATUSES);
26808
+ var MAIL_DELIVERY_STATUSES = [
26809
+ "queued",
26810
+ "sending",
26811
+ "sent",
26812
+ "delivered",
26813
+ "bounced",
26814
+ "failed",
26815
+ "skipped"
26816
+ ];
26817
+ var mailDeliveryStatusSchema = _enum2(MAIL_DELIVERY_STATUSES);
26818
+ var mailCampaignSchema = createSelectSchema(mailCampaigns, {
26819
+ data: record(string2(), unknown()),
26820
+ testSentAt: exports_coerce.date().nullable().default(null),
26821
+ startedAt: exports_coerce.date().nullable(),
26822
+ finishedAt: exports_coerce.date().nullable(),
26823
+ createdAt: exports_coerce.date(),
26824
+ updatedAt: exports_coerce.date()
26825
+ });
26826
+ var mailDeliverySchema = createSelectSchema(mailDeliveries, {
26827
+ data: record(string2(), unknown()),
26828
+ sentAt: exports_coerce.date().nullable(),
26829
+ deliveredAt: exports_coerce.date().nullable(),
26830
+ bouncedAt: exports_coerce.date().nullable(),
26831
+ createdAt: exports_coerce.date()
26832
+ });
26833
+ var mailFooterSchema = object({
26834
+ deleteBy: keyDateDaySchema.optional(),
26835
+ contact: email2().max(320).optional()
26836
+ }).strict();
26837
+ var lineSchema = string2().trim().min(1).max(2000);
26838
+ var mailMessageCopySchema = object({
26839
+ subject: string2().trim().min(1).max(200),
26840
+ heading: string2().trim().min(1).max(200),
26841
+ paragraphs: array(lineSchema).max(20).default([]),
26842
+ button: object({ label: string2().trim().min(1).max(80), url: url() }).strict().nullish()
26843
+ }).strict();
26844
+ var mailMessageDataSchema = object({
26845
+ nl: mailMessageCopySchema,
26846
+ pt: mailMessageCopySchema.optional(),
26847
+ footer: mailFooterSchema.optional()
26848
+ }).strict();
26849
+ var mailProviderEventSchema = looseObject({
26850
+ type: string2().min(1),
26851
+ created_at: string2().optional(),
26852
+ data: looseObject({
26853
+ email_id: string2().min(1),
26854
+ to: array(string2()).optional(),
26855
+ bounce: looseObject({
26856
+ type: string2().optional(),
26857
+ message: string2().optional()
26858
+ }).optional()
26859
+ })
26860
+ });
26861
+ var MAIL_CAMPAIGN_KINDS = ["message", "save_the_date", "invitation"];
26862
+ var mailCampaignKindSchema = _enum2(MAIL_CAMPAIGN_KINDS);
26863
+ var MAIL_TEMPLATES = ["message"];
26864
+ var mailTemplateSchema = _enum2(MAIL_TEMPLATES);
26865
+ var mailTemplateDataSchemas = {
26866
+ message: mailMessageDataSchema
26867
+ };
26868
+ function mailTemplateDataError(template, data) {
26869
+ const parsed = mailTemplateDataSchemas[template].safeParse(data);
26870
+ return parsed.success ? null : parsed.error.issues[0]?.message ?? "invalid data";
26871
+ }
26872
+ var replyToSchema = email2().max(320);
26873
+ var createMailCampaignInputSchema = object({
26874
+ id: ulidSchema,
26875
+ guestListId: ulidSchema,
26876
+ kind: mailCampaignKindSchema.default("message"),
26877
+ template: mailTemplateSchema.default("message"),
26878
+ data: record(string2(), unknown()),
26879
+ replyTo: replyToSchema.nullish()
26880
+ }).strict().superRefine((input, ctx) => {
26881
+ const error = mailTemplateDataError(input.template, input.data);
26882
+ if (error)
26883
+ ctx.addIssue({ code: "custom", path: ["data"], message: error });
26884
+ });
26885
+ var updateMailCampaignInputSchema = object({
26886
+ kind: mailCampaignKindSchema.optional(),
26887
+ data: record(string2(), unknown()).optional(),
26888
+ replyTo: replyToSchema.nullish()
26889
+ }).strict();
26890
+ var mailCampaignsQuerySchema = object({ guestListId: ulidSchema }).strict();
26891
+ var mailPreviewQuerySchema = object({ language: mailLanguageSchema.default("nl") }).strict();
26892
+ var mailPreviewSchema = object({
26893
+ language: mailLanguageSchema,
26894
+ subject: string2(),
26895
+ html: string2(),
26896
+ text: string2()
26897
+ });
26898
+ var mailTestResultSchema = object({ to: array(string2()) });
26899
+ var sendMailCampaignInputSchema = object({ households: number2().int().positive() }).strict();
26900
+ var householdMailStatusSchema = _enum2([
26901
+ "queued",
26902
+ "sent",
26903
+ "delivered",
26904
+ "bounced",
26905
+ "failed",
26906
+ "skipped"
26907
+ ]);
26908
+ var mailRecipientHouseholdSchema = object({
26909
+ householdId: string2().nullable(),
26910
+ name: string2(),
26911
+ language: mailLanguageSchema,
26912
+ addresses: number2().int().nonnegative(),
26913
+ status: householdMailStatusSchema.nullable()
26914
+ });
26915
+ var mailCampaignViewSchema = mailCampaignSchema.extend({
26916
+ households: array(mailRecipientHouseholdSchema),
26917
+ counts: partialRecord(mailDeliveryStatusSchema, number2().int().nonnegative()),
26918
+ readyToSend: boolean3()
26919
+ });
26920
+
26742
26921
  // ../../packages/schema/src/contracts/calendar.ts
26743
26922
  var calendarEventLinkSchema = createSelectSchema(calendarEventLinks, {
26744
26923
  createdAt: exports_coerce.date(),
@@ -28439,7 +28618,7 @@ function parseCapture2(input, now) {
28439
28618
 
28440
28619
  // src/meta.ts
28441
28620
  var CLI_NAME = "do";
28442
- var VERSION = "1.285.0";
28621
+ var VERSION = "1.286.0";
28443
28622
  var API_PREFIX = "/api/v1";
28444
28623
  var DEFAULT_API_URL = "http://localhost:3001";
28445
28624
 
@@ -33828,6 +34007,61 @@ var COMMAND_SPECS = [
33828
34007
  scopes: ["contacts:write"],
33829
34008
  returns: "SeatingProposal"
33830
34009
  },
34010
+ {
34011
+ path: ["mail-campaigns", "list"],
34012
+ summary: "A guest list's mail campaigns (save-the-dates, invitations), newest first.",
34013
+ args: [],
34014
+ flags: [{ name: "list", type: "string", description: "The guest list's id (required)." }],
34015
+ scopes: ["contacts:read"],
34016
+ returns: "MailCampaignView",
34017
+ notes: "Each campaign carries the households it reaches (a draft: who it would go to) with their status, its mails by status, and readyToSend."
34018
+ },
34019
+ {
34020
+ path: ["mail-campaigns", "get"],
34021
+ summary: "One mail campaign, with its households and how far it got.",
34022
+ args: [{ name: "id", required: true, description: "The campaign's id." }],
34023
+ flags: [],
34024
+ scopes: ["contacts:read"],
34025
+ returns: "MailCampaignView"
34026
+ },
34027
+ {
34028
+ path: ["mail-campaigns", "create"],
34029
+ summary: "Draft a mail campaign for a guest list; nothing is sent.",
34030
+ args: [],
34031
+ flags: [
34032
+ {
34033
+ name: "data",
34034
+ type: "string",
34035
+ description: 'JSON CreateMailCampaignInput, e.g. {"guestListId":"01\u2026","kind":"save_the_date","template":"message","data":{"nl":{"subject":"\u2026","heading":"\u2026","paragraphs":["\u2026"]},"pt":{\u2026},"footer":{"deleteBy":"2027-12-31"}},"replyTo":"hallo@example.com"}'
34036
+ }
34037
+ ],
34038
+ scopes: ["contacts:write"],
34039
+ returns: "MailCampaignView",
34040
+ notes: "Drafting only: a member previews it, sends a test to the Space's members and sends it from the Mail tab. An agent can never test or send."
34041
+ },
34042
+ {
34043
+ path: ["mail-campaigns", "update"],
34044
+ summary: "Edit a draft campaign (its copy, kind or reply address).",
34045
+ args: [{ name: "id", required: true, description: "The campaign's id." }],
34046
+ flags: [
34047
+ {
34048
+ name: "data",
34049
+ type: "string",
34050
+ description: 'JSON UpdateMailCampaignInput, e.g. {"data":{"nl":{\u2026}}}'
34051
+ }
34052
+ ],
34053
+ scopes: ["contacts:write"],
34054
+ returns: "MailCampaignView",
34055
+ notes: "Only a draft changes (409 otherwise); an edit makes the last test stale."
34056
+ },
34057
+ {
34058
+ path: ["mail-campaigns", "preview"],
34059
+ summary: "Draw a campaign's mail as its first household would get it.",
34060
+ args: [{ name: "id", required: true, description: "The campaign's id." }],
34061
+ flags: [{ name: "language", type: "string", description: "nl (default) or pt." }],
34062
+ scopes: ["contacts:read"],
34063
+ returns: "MailPreview"
34064
+ },
33831
34065
  {
33832
34066
  path: ["guest-lists", "thank-you"],
33833
34067
  summary: "The gifts for the event: the cards still to send, then the ones sent.",
@@ -39006,6 +39240,42 @@ var HANDLERS = {
39006
39240
  render: renderRecord
39007
39241
  };
39008
39242
  },
39243
+ "mail-campaigns list": async ({ flags, requireClient }) => {
39244
+ const list = flagString(flags, "list");
39245
+ if (!list)
39246
+ throw usageError("mail-campaigns list needs --list <guest list id>");
39247
+ parseInput(mailCampaignsQuerySchema, { guestListId: list });
39248
+ return {
39249
+ data: await requireClient().get("/mail-campaigns", { query: { guestListId: list } }),
39250
+ render: renderRecord
39251
+ };
39252
+ },
39253
+ "mail-campaigns get": async ({ args, requireClient }) => {
39254
+ const id = requireArg(args, 0, "campaign id");
39255
+ return { data: await requireClient().get(`/mail-campaigns/${id}`), render: renderRecord };
39256
+ },
39257
+ "mail-campaigns create": async ({ flags, requireClient }) => {
39258
+ const body = parseInput(createMailCampaignInputSchema, { id: ulid3(), ...readDataFlag(flags) });
39259
+ return { data: await requireClient().post("/mail-campaigns", body), render: renderRecord };
39260
+ },
39261
+ "mail-campaigns update": async ({ args, flags, requireClient }) => {
39262
+ const id = requireArg(args, 0, "campaign id");
39263
+ const body = parseInput(updateMailCampaignInputSchema, readDataFlag(flags));
39264
+ return {
39265
+ data: await requireClient().patch(`/mail-campaigns/${id}`, body),
39266
+ render: renderRecord
39267
+ };
39268
+ },
39269
+ "mail-campaigns preview": async ({ args, flags, requireClient }) => {
39270
+ const id = requireArg(args, 0, "campaign id");
39271
+ const query = parseInput(mailPreviewQuerySchema, {
39272
+ language: flagString(flags, "language") ?? "nl"
39273
+ });
39274
+ return {
39275
+ data: await requireClient().get(`/mail-campaigns/${id}/preview`, { query }),
39276
+ render: renderRecord
39277
+ };
39278
+ },
39009
39279
  "guest-lists thank-you": async ({ args, requireClient }) => {
39010
39280
  const id = requireArg(args, 0, "guest list id");
39011
39281
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robbeverhelst/do",
3
- "version": "1.285.0",
3
+ "version": "1.286.0",
4
4
  "private": false,
5
5
  "description": "do — the agent-drivable CLI over the public /api/v1 surface",
6
6
  "homepage": "https://github.com/robbeverhelst/do/tree/main/apps/cli",