feat: add contactBooks to sdk, add delete campaign public endpoint (#352)
* feat: add contactBooks to sdk, add delete campaign public endpoint * fix: pr review notes * refactor: pr feedback * feat: bulk delete/create contacts * refactor: rename a few methods for consistency * refactor: update openapi docs based on pr feedback * refactor: update open api docs, based on pr feedback * fix: delete campaign security issue * refactor: delete campaign requires team id (from context) * fix: enums
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
openapi: delete /v1/campaigns/{campaignId}
|
||||||
|
---
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
openapi: post /v1/contactBooks/{contactBookId}/contacts/bulk
|
||||||
|
---
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
openapi: delete /v1/contactBooks/{contactBookId}/contacts/bulk
|
||||||
|
---
|
||||||
@@ -1585,6 +1585,163 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/contactBooks/{contactBookId}/contacts/bulk": {
|
||||||
|
"delete": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"example": "cuiwqdj74rygf74"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"name": "contactBookId",
|
||||||
|
"in": "path"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"contactIds": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["contactIds"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Bulk delete contacts from a contact book",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"success": { "type": "boolean" },
|
||||||
|
"count": { "type": "number" }
|
||||||
|
},
|
||||||
|
"required": ["success", "count"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden - API key doesn't have access",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": { "error": { "type": "string" } },
|
||||||
|
"required": ["error"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Contact book not found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": { "error": { "type": "string" } },
|
||||||
|
"required": ["error"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"example": "cuiwqdj74rygf74"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"name": "contactBookId",
|
||||||
|
"in": "path"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": { "type": "string" },
|
||||||
|
"firstName": { "type": "string" },
|
||||||
|
"lastName": { "type": "string" },
|
||||||
|
"properties": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": { "type": "string" }
|
||||||
|
},
|
||||||
|
"subscribed": { "type": "boolean" }
|
||||||
|
},
|
||||||
|
"required": ["email"]
|
||||||
|
},
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Bulk add contacts to a contact book",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": { "type": "string" },
|
||||||
|
"count": { "type": "number" }
|
||||||
|
},
|
||||||
|
"required": ["message", "count"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden - API key doesn't have access",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": { "error": { "type": "string" } },
|
||||||
|
"required": ["error"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Contact book not found",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": { "error": { "type": "string" } },
|
||||||
|
"required": ["error"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/contactBooks/{contactBookId}/contacts/{contactId}": {
|
"/v1/contactBooks/{contactBookId}/contacts/{contactId}": {
|
||||||
"patch": {
|
"patch": {
|
||||||
"parameters": [
|
"parameters": [
|
||||||
@@ -1790,10 +1947,9 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"DRAFT",
|
"DRAFT",
|
||||||
"SCHEDULED",
|
"SCHEDULED",
|
||||||
"IN_PROGRESS",
|
"RUNNING",
|
||||||
"PAUSED",
|
"PAUSED",
|
||||||
"COMPLETED",
|
"SENT"
|
||||||
"CANCELLED"
|
|
||||||
],
|
],
|
||||||
"example": "DRAFT"
|
"example": "DRAFT"
|
||||||
},
|
},
|
||||||
@@ -1829,7 +1985,7 @@
|
|||||||
"subject": { "type": "string" },
|
"subject": { "type": "string" },
|
||||||
"createdAt": { "type": "string", "format": "date-time" },
|
"createdAt": { "type": "string", "format": "date-time" },
|
||||||
"updatedAt": { "type": "string", "format": "date-time" },
|
"updatedAt": { "type": "string", "format": "date-time" },
|
||||||
"status": { "type": "string" },
|
"status": { "type": "string", "enum": ["DRAFT", "SCHEDULED", "RUNNING", "PAUSED", "SENT"] },
|
||||||
"scheduledAt": { "type": "string", "nullable": true, "format": "date-time" },
|
"scheduledAt": { "type": "string", "nullable": true, "format": "date-time" },
|
||||||
"total": { "type": "integer" },
|
"total": { "type": "integer" },
|
||||||
"sent": { "type": "integer" },
|
"sent": { "type": "integer" },
|
||||||
@@ -1935,7 +2091,7 @@
|
|||||||
"contactBookId": { "type": "string", "nullable": true },
|
"contactBookId": { "type": "string", "nullable": true },
|
||||||
"html": { "type": "string", "nullable": true },
|
"html": { "type": "string", "nullable": true },
|
||||||
"content": { "type": "string", "nullable": true },
|
"content": { "type": "string", "nullable": true },
|
||||||
"status": { "type": "string" },
|
"status": { "type": "string", "enum": ["DRAFT", "SCHEDULED", "RUNNING", "PAUSED", "SENT"] },
|
||||||
"scheduledAt": {
|
"scheduledAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
@@ -1997,7 +2153,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/campaigns/{campaignId}": {
|
"/v1/campaigns/{campaignId}": {
|
||||||
"get": {
|
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"schema": {
|
"schema": {
|
||||||
@@ -2010,6 +2165,7 @@
|
|||||||
"in": "path"
|
"in": "path"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"get": {
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Get campaign details",
|
"description": "Get campaign details",
|
||||||
@@ -2026,7 +2182,84 @@
|
|||||||
"contactBookId": { "type": "string", "nullable": true },
|
"contactBookId": { "type": "string", "nullable": true },
|
||||||
"html": { "type": "string", "nullable": true },
|
"html": { "type": "string", "nullable": true },
|
||||||
"content": { "type": "string", "nullable": true },
|
"content": { "type": "string", "nullable": true },
|
||||||
"status": { "type": "string" },
|
"status": { "type": "string", "enum": ["DRAFT", "SCHEDULED", "RUNNING", "PAUSED", "SENT"] },
|
||||||
|
"scheduledAt": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"batchSize": { "type": "integer" },
|
||||||
|
"batchWindowMinutes": { "type": "integer" },
|
||||||
|
"total": { "type": "integer" },
|
||||||
|
"sent": { "type": "integer" },
|
||||||
|
"delivered": { "type": "integer" },
|
||||||
|
"opened": { "type": "integer" },
|
||||||
|
"clicked": { "type": "integer" },
|
||||||
|
"unsubscribed": { "type": "integer" },
|
||||||
|
"bounced": { "type": "integer" },
|
||||||
|
"hardBounced": { "type": "integer" },
|
||||||
|
"complained": { "type": "integer" },
|
||||||
|
"replyTo": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" }
|
||||||
|
},
|
||||||
|
"cc": { "type": "array", "items": { "type": "string" } },
|
||||||
|
"bcc": { "type": "array", "items": { "type": "string" } },
|
||||||
|
"createdAt": { "type": "string", "format": "date-time" },
|
||||||
|
"updatedAt": { "type": "string", "format": "date-time" }
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"from",
|
||||||
|
"subject",
|
||||||
|
"previewText",
|
||||||
|
"contactBookId",
|
||||||
|
"html",
|
||||||
|
"content",
|
||||||
|
"status",
|
||||||
|
"scheduledAt",
|
||||||
|
"batchSize",
|
||||||
|
"batchWindowMinutes",
|
||||||
|
"total",
|
||||||
|
"sent",
|
||||||
|
"delivered",
|
||||||
|
"opened",
|
||||||
|
"clicked",
|
||||||
|
"unsubscribed",
|
||||||
|
"bounced",
|
||||||
|
"hardBounced",
|
||||||
|
"complained",
|
||||||
|
"replyTo",
|
||||||
|
"cc",
|
||||||
|
"bcc",
|
||||||
|
"createdAt",
|
||||||
|
"updatedAt"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Delete campaign",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string" },
|
||||||
|
"name": { "type": "string" },
|
||||||
|
"from": { "type": "string" },
|
||||||
|
"subject": { "type": "string" },
|
||||||
|
"previewText": { "type": "string", "nullable": true },
|
||||||
|
"contactBookId": { "type": "string", "nullable": true },
|
||||||
|
"html": { "type": "string", "nullable": true },
|
||||||
|
"content": { "type": "string", "nullable": true },
|
||||||
|
"status": { "type": "string", "enum": ["DRAFT", "SCHEDULED", "RUNNING", "PAUSED", "SENT"] },
|
||||||
"scheduledAt": {
|
"scheduledAt": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
|
|||||||
+5
-2
@@ -77,9 +77,11 @@
|
|||||||
"api-reference/contacts/get-contact",
|
"api-reference/contacts/get-contact",
|
||||||
"api-reference/contacts/get-contacts",
|
"api-reference/contacts/get-contacts",
|
||||||
"api-reference/contacts/create-contact",
|
"api-reference/contacts/create-contact",
|
||||||
|
"api-reference/contacts/bulk-create-contact",
|
||||||
"api-reference/contacts/update-contact",
|
"api-reference/contacts/update-contact",
|
||||||
"api-reference/contacts/upsert-contact",
|
"api-reference/contacts/upsert-contact",
|
||||||
"api-reference/contacts/delete-contact"
|
"api-reference/contacts/delete-contact",
|
||||||
|
"api-reference/contacts/bulk-delete-contacts"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -100,7 +102,8 @@
|
|||||||
"api-reference/campaigns/get-campaign",
|
"api-reference/campaigns/get-campaign",
|
||||||
"api-reference/campaigns/schedule-campaign",
|
"api-reference/campaigns/schedule-campaign",
|
||||||
"api-reference/campaigns/pause-campaign",
|
"api-reference/campaigns/pause-campaign",
|
||||||
"api-reference/campaigns/resume-campaign"
|
"api-reference/campaigns/resume-campaign",
|
||||||
|
"api-reference/campaigns/delete-campaign"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -171,8 +171,8 @@ export const campaignRouter = createTRPCRouter({
|
|||||||
return campaign;
|
return campaign;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
deleteCampaign: campaignProcedure.mutation(async ({ input }) => {
|
deleteCampaign: campaignProcedure.mutation(async ({ ctx: { team }, input }) => {
|
||||||
return await campaignService.deleteCampaign(input.campaignId);
|
return await campaignService.deleteCampaign(input.campaignId, team.id);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getCampaign: campaignProcedure.query(async ({ ctx: { db, team }, input }) => {
|
getCampaign: campaignProcedure.query(async ({ ctx: { db, team }, input }) => {
|
||||||
|
|||||||
@@ -194,6 +194,19 @@ export const contactsRouter = createTRPCRouter({
|
|||||||
return deletedContact;
|
return deletedContact;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
bulkDeleteContacts: contactBookProcedure
|
||||||
|
.input(z.object({ contactIds: z.array(z.string()).min(1).max(1000) }))
|
||||||
|
.mutation(async ({ ctx: { contactBook, team }, input }) => {
|
||||||
|
const deletedContacts =
|
||||||
|
await contactService.bulkDeleteContactsInContactBook(
|
||||||
|
input.contactIds,
|
||||||
|
contactBook.id,
|
||||||
|
team.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return { count: deletedContacts.length };
|
||||||
|
}),
|
||||||
|
|
||||||
resendDoubleOptInConfirmation: contactBookProcedure
|
resendDoubleOptInConfirmation: contactBookProcedure
|
||||||
.input(z.object({ contactId: z.string() }))
|
.input(z.object({ contactId: z.string() }))
|
||||||
.mutation(async ({ ctx: { contactBook, team }, input }) => {
|
.mutation(async ({ ctx: { contactBook, team }, input }) => {
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
|
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||||
|
import { deleteCampaign } from "~/server/service/campaign-service";
|
||||||
|
import { campaignResponseSchema } from "~/server/public-api/schemas/campaign-schema";
|
||||||
|
|
||||||
|
const route = createRoute({
|
||||||
|
method: "delete",
|
||||||
|
path: "/v1/campaigns/{campaignId}",
|
||||||
|
request: {
|
||||||
|
params: z.object({
|
||||||
|
campaignId: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.openapi({
|
||||||
|
param: {
|
||||||
|
name: "campaignId",
|
||||||
|
in: "path",
|
||||||
|
},
|
||||||
|
example: "cmp_123",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Delete campaign",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: campaignResponseSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function deleteCampaignHandle(app: PublicAPIApp) {
|
||||||
|
app.openapi(route, async (c) => {
|
||||||
|
const team = c.var.team;
|
||||||
|
const campaignId = c.req.param("campaignId");
|
||||||
|
|
||||||
|
const campaign = await deleteCampaign(campaignId, team.id);
|
||||||
|
return c.json(campaign);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deleteCampaignHandle;
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
import { createRoute, z } from "@hono/zod-openapi";
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
import { PublicAPIApp } from "~/server/public-api/hono";
|
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||||
import {
|
import {
|
||||||
getCampaignForTeam,
|
pauseCampaign,
|
||||||
pauseCampaign as pauseCampaignService,
|
|
||||||
} from "~/server/service/campaign-service";
|
} from "~/server/service/campaign-service";
|
||||||
import { campaignResponseSchema } from "~/server/public-api/schemas/campaign-schema";
|
|
||||||
|
|
||||||
const route = createRoute({
|
const route = createRoute({
|
||||||
method: "post",
|
method: "post",
|
||||||
@@ -37,12 +35,12 @@ const route = createRoute({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function pauseCampaign(app: PublicAPIApp) {
|
function pauseCampaignHandle(app: PublicAPIApp) {
|
||||||
app.openapi(route, async (c) => {
|
app.openapi(route, async (c) => {
|
||||||
const team = c.var.team;
|
const team = c.var.team;
|
||||||
const campaignId = c.req.param("campaignId");
|
const campaignId = c.req.param("campaignId");
|
||||||
|
|
||||||
await pauseCampaignService({
|
await pauseCampaign({
|
||||||
campaignId,
|
campaignId,
|
||||||
teamId: team.id,
|
teamId: team.id,
|
||||||
});
|
});
|
||||||
@@ -51,4 +49,4 @@ function pauseCampaign(app: PublicAPIApp) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default pauseCampaign;
|
export default pauseCampaignHandle;
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import { createRoute, z } from "@hono/zod-openapi";
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
import { PublicAPIApp } from "~/server/public-api/hono";
|
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||||
import {
|
import {
|
||||||
getCampaignForTeam,
|
resumeCampaign,
|
||||||
resumeCampaign as resumeCampaignService,
|
|
||||||
} from "~/server/service/campaign-service";
|
} from "~/server/service/campaign-service";
|
||||||
import {
|
|
||||||
campaignResponseSchema,
|
|
||||||
parseScheduledAt,
|
|
||||||
} from "~/server/public-api/schemas/campaign-schema";
|
|
||||||
|
|
||||||
const route = createRoute({
|
const route = createRoute({
|
||||||
method: "post",
|
method: "post",
|
||||||
@@ -40,17 +35,12 @@ const route = createRoute({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function resumeCampaign(app: PublicAPIApp) {
|
function resumeCampaignHandle(app: PublicAPIApp) {
|
||||||
app.openapi(route, async (c) => {
|
app.openapi(route, async (c) => {
|
||||||
const team = c.var.team;
|
const team = c.var.team;
|
||||||
const campaignId = c.req.param("campaignId");
|
const campaignId = c.req.param("campaignId");
|
||||||
|
|
||||||
await resumeCampaignService({
|
await resumeCampaign({
|
||||||
campaignId,
|
|
||||||
teamId: team.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await getCampaignForTeam({
|
|
||||||
campaignId,
|
campaignId,
|
||||||
teamId: team.id,
|
teamId: team.id,
|
||||||
});
|
});
|
||||||
@@ -59,4 +49,4 @@ function resumeCampaign(app: PublicAPIApp) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default resumeCampaign;
|
export default resumeCampaignHandle;
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ import { PublicAPIApp } from "~/server/public-api/hono";
|
|||||||
import {
|
import {
|
||||||
campaignScheduleSchema,
|
campaignScheduleSchema,
|
||||||
CampaignScheduleInput,
|
CampaignScheduleInput,
|
||||||
campaignResponseSchema,
|
|
||||||
parseScheduledAt,
|
parseScheduledAt,
|
||||||
} from "~/server/public-api/schemas/campaign-schema";
|
} from "~/server/public-api/schemas/campaign-schema";
|
||||||
import {
|
import {
|
||||||
getCampaignForTeam,
|
|
||||||
scheduleCampaign as scheduleCampaignService,
|
scheduleCampaign as scheduleCampaignService,
|
||||||
} from "~/server/service/campaign-service";
|
} from "~/server/service/campaign-service";
|
||||||
const route = createRoute({
|
const route = createRoute({
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
|
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||||
|
import { bulkAddContacts } from "~/server/service/contact-service";
|
||||||
|
import { getContactBook } from "../../api-utils";
|
||||||
|
|
||||||
|
const contactSchema = z.object({
|
||||||
|
email: z.string(),
|
||||||
|
firstName: z.string().optional(),
|
||||||
|
lastName: z.string().optional(),
|
||||||
|
properties: z.record(z.string()).optional(),
|
||||||
|
subscribed: z.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = createRoute({
|
||||||
|
method: "post",
|
||||||
|
path: "/v1/contactBooks/{contactBookId}/contacts/bulk",
|
||||||
|
request: {
|
||||||
|
params: z.object({
|
||||||
|
contactBookId: z
|
||||||
|
.string()
|
||||||
|
.min(3)
|
||||||
|
.openapi({
|
||||||
|
param: {
|
||||||
|
name: "contactBookId",
|
||||||
|
in: "path",
|
||||||
|
},
|
||||||
|
example: "cuiwqdj74rygf74",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
body: {
|
||||||
|
required: true,
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.array(contactSchema).max(1000, {
|
||||||
|
message:
|
||||||
|
"Cannot add more than 1000 contacts in a single bulk request",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
message: z.string(),
|
||||||
|
count: z.number(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Bulk add contacts to a contact book",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function bulkAddContactsHandle(app: PublicAPIApp) {
|
||||||
|
app.openapi(route, async (c) => {
|
||||||
|
const team = c.var.team;
|
||||||
|
|
||||||
|
const contactBook = await getContactBook(c, team.id);
|
||||||
|
|
||||||
|
const result = await bulkAddContacts(
|
||||||
|
contactBook.id,
|
||||||
|
c.req.valid("json"),
|
||||||
|
team.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default bulkAddContactsHandle;
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
|
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||||
|
import { bulkDeleteContactsInContactBook } from "~/server/service/contact-service";
|
||||||
|
import { getContactBook } from "../../api-utils";
|
||||||
|
|
||||||
|
const route = createRoute({
|
||||||
|
method: "delete",
|
||||||
|
path: "/v1/contactBooks/{contactBookId}/contacts/bulk",
|
||||||
|
request: {
|
||||||
|
params: z.object({
|
||||||
|
contactBookId: z
|
||||||
|
.string()
|
||||||
|
.min(3)
|
||||||
|
.openapi({
|
||||||
|
param: {
|
||||||
|
name: "contactBookId",
|
||||||
|
in: "path",
|
||||||
|
},
|
||||||
|
example: "cuiwqdj74rygf74",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
body: {
|
||||||
|
required: true,
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
contactIds: z.array(z.string()).min(1).max(1000, {
|
||||||
|
message:
|
||||||
|
"Cannot delete more than 1000 contacts in a single request",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
success: z.boolean(),
|
||||||
|
count: z.number(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Bulk delete contacts from a contact book",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function bulkDeleteContacts(app: PublicAPIApp) {
|
||||||
|
app.openapi(route, async (c) => {
|
||||||
|
const team = c.var.team;
|
||||||
|
|
||||||
|
const contactBook = await getContactBook(c, team.id);
|
||||||
|
|
||||||
|
const deletedContacts = await bulkDeleteContactsInContactBook(
|
||||||
|
c.req.valid("json").contactIds,
|
||||||
|
contactBook.id,
|
||||||
|
team.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json({ success: true, count: deletedContacts.length });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default bulkDeleteContacts;
|
||||||
@@ -18,15 +18,18 @@ import deleteDomain from "./api/domains/delete-domain";
|
|||||||
import sendBatch from "./api/emails/batch-email";
|
import sendBatch from "./api/emails/batch-email";
|
||||||
import createCampaign from "./api/campaigns/create-campaign";
|
import createCampaign from "./api/campaigns/create-campaign";
|
||||||
import getCampaign from "./api/campaigns/get-campaign";
|
import getCampaign from "./api/campaigns/get-campaign";
|
||||||
|
import deleteCampaignHandle from "./api/campaigns/delete-campaign";
|
||||||
import getCampaigns from "./api/campaigns/get-campaigns";
|
import getCampaigns from "./api/campaigns/get-campaigns";
|
||||||
import scheduleCampaign from "./api/campaigns/schedule-campaign";
|
import scheduleCampaign from "./api/campaigns/schedule-campaign";
|
||||||
import pauseCampaign from "./api/campaigns/pause-campaign";
|
import pauseCampaignHandle from "./api/campaigns/pause-campaign";
|
||||||
import resumeCampaign from "./api/campaigns/resume-campaign";
|
import resumeCampaignHandle from "./api/campaigns/resume-campaign";
|
||||||
import getContactBooks from "./api/contacts/get-contact-books";
|
import getContactBooks from "./api/contacts/get-contact-books";
|
||||||
import createContactBook from "./api/contacts/create-contact-book";
|
import createContactBook from "./api/contacts/create-contact-book";
|
||||||
import getContactBook from "./api/contacts/get-contact-book";
|
import getContactBook from "./api/contacts/get-contact-book";
|
||||||
import updateContactBook from "./api/contacts/update-contact-book";
|
import updateContactBook from "./api/contacts/update-contact-book";
|
||||||
import deleteContactBook from "./api/contacts/delete-contact-book";
|
import deleteContactBook from "./api/contacts/delete-contact-book";
|
||||||
|
import bulkAddContactsHandle from "./api/contacts/bulk-add-contacts";
|
||||||
|
import bulkDeleteContacts from "./api/contacts/bulk-delete-contacts";
|
||||||
|
|
||||||
export const app = getApp();
|
export const app = getApp();
|
||||||
|
|
||||||
@@ -52,6 +55,8 @@ getContact(app);
|
|||||||
getContacts(app);
|
getContacts(app);
|
||||||
upsertContact(app);
|
upsertContact(app);
|
||||||
deleteContact(app);
|
deleteContact(app);
|
||||||
|
bulkAddContactsHandle(app);
|
||||||
|
bulkDeleteContacts(app);
|
||||||
|
|
||||||
/**Contact Book related APIs */
|
/**Contact Book related APIs */
|
||||||
getContactBooks(app);
|
getContactBooks(app);
|
||||||
@@ -65,7 +70,8 @@ createCampaign(app);
|
|||||||
getCampaign(app);
|
getCampaign(app);
|
||||||
getCampaigns(app);
|
getCampaigns(app);
|
||||||
scheduleCampaign(app);
|
scheduleCampaign(app);
|
||||||
pauseCampaign(app);
|
pauseCampaignHandle(app);
|
||||||
resumeCampaign(app);
|
resumeCampaignHandle(app);
|
||||||
|
deleteCampaignHandle(app);
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
@@ -669,7 +669,18 @@ export async function subscribeContact(id: string, hash: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCampaign(id: string) {
|
export async function deleteCampaign(id: string, teamId: number) {
|
||||||
|
const existing = await db.campaign.findFirst({
|
||||||
|
where: { id, teamId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
throw new UnsendApiError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Campaign not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const campaign = await db.$transaction(async (tx) => {
|
const campaign = await db.$transaction(async (tx) => {
|
||||||
await tx.campaignEmail.deleteMany({
|
await tx.campaignEmail.deleteMany({
|
||||||
where: { campaignId: id },
|
where: { campaignId: id },
|
||||||
|
|||||||
@@ -214,6 +214,38 @@ export async function deleteContactInContactBook(
|
|||||||
return deletedContact;
|
return deletedContact;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function bulkDeleteContactsInContactBook(
|
||||||
|
contactIds: string[],
|
||||||
|
contactBookId: string,
|
||||||
|
teamId?: number,
|
||||||
|
) {
|
||||||
|
const contacts = await db.contact.findMany({
|
||||||
|
where: {
|
||||||
|
id: { in: contactIds },
|
||||||
|
contactBookId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (contacts.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.contact.deleteMany({
|
||||||
|
where: {
|
||||||
|
id: { in: contacts.map((c) => c.id) },
|
||||||
|
contactBookId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
contacts.map((contact) =>
|
||||||
|
emitContactEvent(contact, "contact.deleted", teamId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return contacts;
|
||||||
|
}
|
||||||
|
|
||||||
export async function resendDoubleOptInConfirmationInContactBook(
|
export async function resendDoubleOptInConfirmationInContactBook(
|
||||||
contactId: string,
|
contactId: string,
|
||||||
contactBookId: string,
|
contactBookId: string,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export { UseSend } from "./src/usesend";
|
export { UseSend } from "./src/usesend";
|
||||||
export { UseSend as Unsend } from "./src/usesend"; // deprecated alias
|
export { UseSend as Unsend } from "./src/usesend"; // deprecated alias
|
||||||
export { Campaigns } from "./src/campaign";
|
export { Campaigns } from "./src/campaign";
|
||||||
|
export { ContactBooks } from "./src/contactBook";
|
||||||
export {
|
export {
|
||||||
Webhooks,
|
Webhooks,
|
||||||
WebhookVerificationError,
|
WebhookVerificationError,
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ type CreateCampaignResponse = {
|
|||||||
type CreateCampaignResponseSuccess =
|
type CreateCampaignResponseSuccess =
|
||||||
paths["/v1/campaigns"]["post"]["responses"]["200"]["content"]["application/json"];
|
paths["/v1/campaigns"]["post"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type GetAllCampaignsQuery = {
|
||||||
|
page?: string;
|
||||||
|
status?: NonNullable<paths["/v1/campaigns"]["get"]["parameters"]["query"]>["status"];
|
||||||
|
search?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GetAllCampaignsResponseSuccess = paths["/v1/campaigns"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type GetAllCampaignsResponse = {
|
||||||
|
data: GetAllCampaignsResponseSuccess | null;
|
||||||
|
error: ErrorResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
type GetCampaignResponseSuccess =
|
type GetCampaignResponseSuccess =
|
||||||
paths["/v1/campaigns/{campaignId}"]["get"]["responses"]["200"]["content"]["application/json"];
|
paths["/v1/campaigns/{campaignId}"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
@@ -32,6 +45,14 @@ type ScheduleCampaignResponse = {
|
|||||||
error: ErrorResponse | null;
|
error: ErrorResponse | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type DeleteCampaignResponseSuccess =
|
||||||
|
paths["/v1/campaigns/{campaignId}"]["delete"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type DeleteCampaignResponse = {
|
||||||
|
data: DeleteCampaignResponseSuccess | null;
|
||||||
|
error: ErrorResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
type CampaignActionResponseSuccess = { success: boolean };
|
type CampaignActionResponseSuccess = { success: boolean };
|
||||||
|
|
||||||
type CampaignActionResponse = {
|
type CampaignActionResponse = {
|
||||||
@@ -55,6 +76,21 @@ export class Campaigns {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAll(
|
||||||
|
query?: GetAllCampaignsQuery,
|
||||||
|
): Promise<GetAllCampaignsResponse> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (query?.page) params.set("page", query.page);
|
||||||
|
if (query?.status) params.set("status", query.status);
|
||||||
|
if (query?.search) params.set("search", query.search);
|
||||||
|
|
||||||
|
const queryString = params.toString();
|
||||||
|
const path = queryString ? `/campaigns?${queryString}` : `/campaigns`;
|
||||||
|
|
||||||
|
const data = await this.usesend.get<GetAllCampaignsResponseSuccess>(path);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
async get(campaignId: string): Promise<GetCampaignResponse> {
|
async get(campaignId: string): Promise<GetCampaignResponse> {
|
||||||
const data = await this.usesend.get<GetCampaignResponseSuccess>(
|
const data = await this.usesend.get<GetCampaignResponseSuccess>(
|
||||||
`/campaigns/${campaignId}`,
|
`/campaigns/${campaignId}`,
|
||||||
@@ -91,4 +127,11 @@ export class Campaigns {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async delete(campaignId: string): Promise<DeleteCampaignResponse> {
|
||||||
|
const data = await this.usesend.delete<DeleteCampaignResponseSuccess>(
|
||||||
|
`/campaigns/${campaignId}`,
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,28 @@ type UpsertContactResponse = {
|
|||||||
error: ErrorResponse | null;
|
error: ErrorResponse | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type BulkCreateContactsPayload =
|
||||||
|
paths["/v1/contactBooks/{contactBookId}/contacts/bulk"]["post"]["requestBody"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type BulkCreateContactsResponseSuccess =
|
||||||
|
paths["/v1/contactBooks/{contactBookId}/contacts/bulk"]["post"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type BulkCreateContactsResponse = {
|
||||||
|
data: BulkCreateContactsResponseSuccess | null;
|
||||||
|
error: ErrorResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BulkDeleteContactsPayload =
|
||||||
|
paths["/v1/contactBooks/{contactBookId}/contacts/bulk"]["delete"]["requestBody"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type BulkDeleteContactsResponseSuccess =
|
||||||
|
paths["/v1/contactBooks/{contactBookId}/contacts/bulk"]["delete"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type BulkDeleteContactsResponse = {
|
||||||
|
data: BulkDeleteContactsResponseSuccess | null;
|
||||||
|
error: ErrorResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
type DeleteContactResponse = {
|
type DeleteContactResponse = {
|
||||||
data: { success: boolean } | null;
|
data: { success: boolean } | null;
|
||||||
error: ErrorResponse | null;
|
error: ErrorResponse | null;
|
||||||
@@ -101,6 +123,30 @@ export class Contacts {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async bulkCreate(
|
||||||
|
contactBookId: string,
|
||||||
|
payload: BulkCreateContactsPayload
|
||||||
|
): Promise<BulkCreateContactsResponse> {
|
||||||
|
const data = await this.usesend.post<BulkCreateContactsResponseSuccess>(
|
||||||
|
`/contactBooks/${contactBookId}/contacts/bulk`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async bulkDelete(
|
||||||
|
contactBookId: string,
|
||||||
|
payload: BulkDeleteContactsPayload
|
||||||
|
): Promise<BulkDeleteContactsResponse> {
|
||||||
|
const data = await this.usesend.delete<BulkDeleteContactsResponseSuccess>(
|
||||||
|
`/contactBooks/${contactBookId}/contacts/bulk`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
async delete(
|
async delete(
|
||||||
contactBookId: string,
|
contactBookId: string,
|
||||||
contactId: string
|
contactId: string
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import { UseSend } from "./usesend";
|
||||||
|
import { paths } from "../types/schema";
|
||||||
|
import { ErrorResponse } from "../types";
|
||||||
|
|
||||||
|
type GetAllContactBooksResponseSuccess =
|
||||||
|
paths["/v1/contactBooks"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type GetAllContactBooksResponse = {
|
||||||
|
data: GetAllContactBooksResponseSuccess | null;
|
||||||
|
error: ErrorResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CreateContactBookPayload =
|
||||||
|
paths["/v1/contactBooks"]["post"]["requestBody"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type CreateContactBookResponseSuccess =
|
||||||
|
paths["/v1/contactBooks"]["post"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type CreateContactBookResponse = {
|
||||||
|
data: CreateContactBookResponseSuccess | null;
|
||||||
|
error: ErrorResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GetContactBookResponseSuccess =
|
||||||
|
paths["/v1/contactBooks/{contactBookId}"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type GetContactBookResponse = {
|
||||||
|
data: GetContactBookResponseSuccess | null;
|
||||||
|
error: ErrorResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UpdateContactBookPayload =
|
||||||
|
paths["/v1/contactBooks/{contactBookId}"]["patch"]["requestBody"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type UpdateContactBookResponseSuccess =
|
||||||
|
paths["/v1/contactBooks/{contactBookId}"]["patch"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type UpdateContactBookResponse = {
|
||||||
|
data: UpdateContactBookResponseSuccess | null;
|
||||||
|
error: ErrorResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DeleteContactBookResponseSuccess =
|
||||||
|
paths["/v1/contactBooks/{contactBookId}"]["delete"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type DeleteContactBookResponse = {
|
||||||
|
data: DeleteContactBookResponseSuccess | null;
|
||||||
|
error: ErrorResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ContactBooks {
|
||||||
|
constructor(private readonly usesend: UseSend) {
|
||||||
|
this.usesend = usesend;
|
||||||
|
}
|
||||||
|
|
||||||
|
async list(): Promise<GetAllContactBooksResponse> {
|
||||||
|
const data = await this.usesend.get<GetAllContactBooksResponseSuccess>(
|
||||||
|
`/contactBooks`,
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(contactBookId: string): Promise<GetContactBookResponse> {
|
||||||
|
const data = await this.usesend.get<GetContactBookResponseSuccess>(
|
||||||
|
`/contactBooks/${contactBookId}`,
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(
|
||||||
|
payload: CreateContactBookPayload,
|
||||||
|
): Promise<CreateContactBookResponse> {
|
||||||
|
const data = await this.usesend.post<CreateContactBookResponseSuccess>(
|
||||||
|
`/contactBooks`,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
contactBookId: string,
|
||||||
|
payload: UpdateContactBookPayload,
|
||||||
|
): Promise<UpdateContactBookResponse> {
|
||||||
|
const data = await this.usesend.patch<UpdateContactBookResponseSuccess>(
|
||||||
|
`/contactBooks/${contactBookId}`,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(contactBookId: string): Promise<DeleteContactBookResponse> {
|
||||||
|
const data = await this.usesend.delete<DeleteContactBookResponseSuccess>(
|
||||||
|
`/contactBooks/${contactBookId}`,
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ErrorResponse } from "../types";
|
import { ErrorResponse } from "../types";
|
||||||
import { Contacts } from "./contact";
|
import { Contacts } from "./contact";
|
||||||
|
import { ContactBooks } from "./contactBook";
|
||||||
import { Emails } from "./email";
|
import { Emails } from "./email";
|
||||||
import { Domains } from "./domain";
|
import { Domains } from "./domain";
|
||||||
import { Campaigns } from "./campaign";
|
import { Campaigns } from "./campaign";
|
||||||
@@ -23,6 +24,7 @@ export class UseSend {
|
|||||||
readonly emails = new Emails(this);
|
readonly emails = new Emails(this);
|
||||||
readonly domains = new Domains(this);
|
readonly domains = new Domains(this);
|
||||||
readonly contacts = new Contacts(this);
|
readonly contacts = new Contacts(this);
|
||||||
|
readonly contactBooks = new ContactBooks(this);
|
||||||
readonly campaigns = new Campaigns(this);
|
readonly campaigns = new Campaigns(this);
|
||||||
url = baseUrl;
|
url = baseUrl;
|
||||||
|
|
||||||
|
|||||||
Vendored
+485
-5
@@ -561,7 +561,9 @@ export interface paths {
|
|||||||
post: {
|
post: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: {
|
||||||
|
"Idempotency-Key"?: string;
|
||||||
|
};
|
||||||
path?: never;
|
path?: never;
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
@@ -628,7 +630,9 @@ export interface paths {
|
|||||||
post: {
|
post: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: {
|
||||||
|
"Idempotency-Key"?: string;
|
||||||
|
};
|
||||||
path?: never;
|
path?: never;
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
@@ -724,6 +728,300 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/v1/contactBooks": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Retrieve contact books accessible by the API key */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
/**
|
||||||
|
* @description The ID of the contact book
|
||||||
|
* @example clx1234567890
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* @description The name of the contact book
|
||||||
|
* @example Newsletter Subscribers
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* @description The ID of the team
|
||||||
|
* @example 1
|
||||||
|
*/
|
||||||
|
teamId: number;
|
||||||
|
/**
|
||||||
|
* @description Custom properties for the contact book
|
||||||
|
* @example {
|
||||||
|
* "customField1": "value1"
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
properties: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @description The emoji associated with the contact book
|
||||||
|
* @example 📙
|
||||||
|
*/
|
||||||
|
emoji: string;
|
||||||
|
/** @description The creation timestamp */
|
||||||
|
createdAt: string;
|
||||||
|
/** @description The last update timestamp */
|
||||||
|
updatedAt: string;
|
||||||
|
_count?: {
|
||||||
|
/** @description The number of contacts in the contact book */
|
||||||
|
contacts?: number;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
name: string;
|
||||||
|
emoji?: string;
|
||||||
|
properties?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Create a new contact book */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
teamId: number;
|
||||||
|
properties: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
emoji: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/v1/contactBooks/{contactBookId}": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
contactBookId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Retrieve the contact book */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
teamId: number;
|
||||||
|
properties: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
emoji: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
_count?: {
|
||||||
|
contacts?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden - API key doesn't have access */
|
||||||
|
403: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Contact book not found */
|
||||||
|
404: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
contactBookId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Contact book deleted successfully */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
id: string;
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden - API key doesn't have access */
|
||||||
|
403: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Contact book not found */
|
||||||
|
404: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
contactBookId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
name?: string;
|
||||||
|
emoji?: string;
|
||||||
|
properties?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Update the contact book */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
teamId: number;
|
||||||
|
properties: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
emoji: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden - API key doesn't have access */
|
||||||
|
403: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Contact book not found */
|
||||||
|
404: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/v1/contactBooks/{contactBookId}/contacts": {
|
"/v1/contactBooks/{contactBookId}/contacts": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -814,6 +1112,88 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/v1/contactBooks/{contactBookId}/contacts/bulk": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get?: never;
|
||||||
|
put?: never;
|
||||||
|
post: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
contactBookId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
email: string;
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
properties?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
subscribed?: boolean;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Bulk add contacts to a contact book */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
message: string;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
delete: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
contactBookId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
contactIds: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Bulk delete contacts from a contact book */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
success: boolean;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/v1/contactBooks/{contactBookId}/contacts/{contactId}": {
|
"/v1/contactBooks/{contactBookId}/contacts/{contactId}": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
@@ -863,6 +1243,7 @@ export interface paths {
|
|||||||
header?: never;
|
header?: never;
|
||||||
path: {
|
path: {
|
||||||
contactBookId: string;
|
contactBookId: string;
|
||||||
|
contactId: string;
|
||||||
};
|
};
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
@@ -966,7 +1347,53 @@ export interface paths {
|
|||||||
path?: never;
|
path?: never;
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
get?: never;
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
/** @description Page number for pagination (default: 1) */
|
||||||
|
page?: string;
|
||||||
|
/** @description Filter campaigns by status */
|
||||||
|
status?: "DRAFT" | "SCHEDULED" | "RUNNING" | "PAUSED" | "SENT";
|
||||||
|
/** @description Search campaigns by name or subject */
|
||||||
|
search?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Get list of campaigns */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
campaigns: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
from: string;
|
||||||
|
subject: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
createdAt: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
updatedAt: string;
|
||||||
|
/** @enum {string} */
|
||||||
|
status: "DRAFT" | "SCHEDULED" | "RUNNING" | "PAUSED" | "SENT";
|
||||||
|
/** Format: date-time */
|
||||||
|
scheduledAt: string | null;
|
||||||
|
total: number;
|
||||||
|
sent: number;
|
||||||
|
delivered: number;
|
||||||
|
unsubscribed: number;
|
||||||
|
}[];
|
||||||
|
totalPage: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
put?: never;
|
put?: never;
|
||||||
post: {
|
post: {
|
||||||
parameters: {
|
parameters: {
|
||||||
@@ -1047,7 +1474,9 @@ export interface paths {
|
|||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
header?: never;
|
header?: never;
|
||||||
path?: never;
|
path: {
|
||||||
|
campaignId: string;
|
||||||
|
};
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
get: {
|
get: {
|
||||||
@@ -1104,7 +1533,58 @@ export interface paths {
|
|||||||
};
|
};
|
||||||
put?: never;
|
put?: never;
|
||||||
post?: never;
|
post?: never;
|
||||||
delete?: never;
|
delete: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
campaignId: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Delete campaign */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
from: string;
|
||||||
|
subject: string;
|
||||||
|
previewText: string | null;
|
||||||
|
contactBookId: string | null;
|
||||||
|
html: string | null;
|
||||||
|
content: string | null;
|
||||||
|
status: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
scheduledAt: string | null;
|
||||||
|
batchSize: number;
|
||||||
|
batchWindowMinutes: number;
|
||||||
|
total: number;
|
||||||
|
sent: number;
|
||||||
|
delivered: number;
|
||||||
|
opened: number;
|
||||||
|
clicked: number;
|
||||||
|
unsubscribed: number;
|
||||||
|
bounced: number;
|
||||||
|
hardBounced: number;
|
||||||
|
complained: number;
|
||||||
|
replyTo: string[];
|
||||||
|
cc: string[];
|
||||||
|
bcc: string[];
|
||||||
|
/** Format: date-time */
|
||||||
|
createdAt: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
options?: never;
|
options?: never;
|
||||||
head?: never;
|
head?: never;
|
||||||
patch?: never;
|
patch?: never;
|
||||||
|
|||||||
Reference in New Issue
Block a user