feat: add dashboard analytics to sdk and public api (#353)
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
openapi: get /v1/analytics/email-time-series
|
||||||
|
---
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
openapi: get /v1/analytics/reputation-metrics
|
||||||
|
---
|
||||||
@@ -2430,6 +2430,127 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/v1/analytics/email-time-series": {
|
||||||
|
"get": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["7", "30"],
|
||||||
|
"example": "30"
|
||||||
|
},
|
||||||
|
"required": false,
|
||||||
|
"name": "days",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of days to retrieve data for (default: 30)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"schema": { "type": "string" },
|
||||||
|
"required": false,
|
||||||
|
"name": "domainId",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filter by domain ID"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Retrieve email time series data",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"result": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date": { "type": "string" },
|
||||||
|
"sent": { "type": "integer" },
|
||||||
|
"delivered": { "type": "integer" },
|
||||||
|
"opened": { "type": "integer" },
|
||||||
|
"clicked": { "type": "integer" },
|
||||||
|
"bounced": { "type": "integer" },
|
||||||
|
"complained": { "type": "integer" }
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"date",
|
||||||
|
"sent",
|
||||||
|
"delivered",
|
||||||
|
"opened",
|
||||||
|
"clicked",
|
||||||
|
"bounced",
|
||||||
|
"complained"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"totalCounts": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"sent": { "type": "integer" },
|
||||||
|
"delivered": { "type": "integer" },
|
||||||
|
"opened": { "type": "integer" },
|
||||||
|
"clicked": { "type": "integer" },
|
||||||
|
"bounced": { "type": "integer" },
|
||||||
|
"complained": { "type": "integer" }
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"sent",
|
||||||
|
"delivered",
|
||||||
|
"opened",
|
||||||
|
"clicked",
|
||||||
|
"bounced",
|
||||||
|
"complained"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["result", "totalCounts"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/analytics/reputation-metrics": {
|
||||||
|
"get": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"schema": { "type": "string" },
|
||||||
|
"required": false,
|
||||||
|
"name": "domainId",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filter by domain ID"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Retrieve reputation metrics data",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"delivered": { "type": "integer" },
|
||||||
|
"hardBounced": { "type": "integer" },
|
||||||
|
"complained": { "type": "integer" },
|
||||||
|
"bounceRate": { "type": "number" },
|
||||||
|
"complaintRate": { "type": "number" }
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"delivered",
|
||||||
|
"hardBounced",
|
||||||
|
"complained",
|
||||||
|
"bounceRate",
|
||||||
|
"complaintRate"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,13 @@
|
|||||||
"api-reference/campaigns/resume-campaign",
|
"api-reference/campaigns/resume-campaign",
|
||||||
"api-reference/campaigns/delete-campaign"
|
"api-reference/campaigns/delete-campaign"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Analytics",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/analytics/email-time-series",
|
||||||
|
"api-reference/analytics/reputation-metrics"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Prisma } from "@prisma/client";
|
|
||||||
import { format, subDays } from "date-fns";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createTRPCRouter, teamProcedure } from "~/server/api/trpc";
|
import { createTRPCRouter, teamProcedure } from "~/server/api/trpc";
|
||||||
import { db } from "~/server/db";
|
import { emailTimeSeries, reputationMetricsData } from "~/server/service/dashboard-service";
|
||||||
|
|
||||||
export const dashboardRouter = createTRPCRouter({
|
export const dashboardRouter = createTRPCRouter({
|
||||||
emailTimeSeries: teamProcedure
|
emailTimeSeries: teamProcedure
|
||||||
@@ -15,88 +12,10 @@ export const dashboardRouter = createTRPCRouter({
|
|||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { team } = ctx;
|
const { team } = ctx;
|
||||||
const days = input.days !== 7 ? 30 : 7;
|
|
||||||
|
|
||||||
const startDate = new Date();
|
const response = await emailTimeSeries({team, days: input.days, domain: input.domain})
|
||||||
startDate.setDate(startDate.getDate() - days);
|
|
||||||
const isoStartDate = startDate.toISOString().split("T")[0];
|
|
||||||
|
|
||||||
type DailyEmailUsage = {
|
return response
|
||||||
date: string;
|
|
||||||
sent: number;
|
|
||||||
delivered: number;
|
|
||||||
opened: number;
|
|
||||||
clicked: number;
|
|
||||||
bounced: number;
|
|
||||||
complained: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await db.$queryRaw<Array<DailyEmailUsage>>`
|
|
||||||
SELECT
|
|
||||||
date,
|
|
||||||
SUM(sent)::integer AS sent,
|
|
||||||
SUM(delivered)::integer AS delivered,
|
|
||||||
SUM(opened)::integer AS opened,
|
|
||||||
SUM(clicked)::integer AS clicked,
|
|
||||||
SUM(bounced)::integer AS bounced,
|
|
||||||
SUM(complained)::integer AS complained
|
|
||||||
FROM "DailyEmailUsage"
|
|
||||||
WHERE "teamId" = ${team.id}
|
|
||||||
AND "date" >= ${isoStartDate}
|
|
||||||
${input.domain ? Prisma.sql`AND "domainId" = ${input.domain}` : Prisma.sql``}
|
|
||||||
GROUP BY "date"
|
|
||||||
ORDER BY "date" ASC
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Fill in any missing dates with 0 values
|
|
||||||
const filledResult: DailyEmailUsage[] = [];
|
|
||||||
const endDateObj = new Date();
|
|
||||||
|
|
||||||
for (let i = days; i > -1; i--) {
|
|
||||||
const dateStr = subDays(endDateObj, i)
|
|
||||||
.toISOString()
|
|
||||||
.split("T")[0] as string;
|
|
||||||
const existingData = result.find((r) => r.date === dateStr);
|
|
||||||
|
|
||||||
if (existingData) {
|
|
||||||
filledResult.push({
|
|
||||||
...existingData,
|
|
||||||
date: format(dateStr, "MMM dd"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
filledResult.push({
|
|
||||||
date: format(dateStr, "MMM dd"),
|
|
||||||
sent: 0,
|
|
||||||
delivered: 0,
|
|
||||||
opened: 0,
|
|
||||||
clicked: 0,
|
|
||||||
bounced: 0,
|
|
||||||
complained: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalCounts = result.reduce(
|
|
||||||
(acc, curr) => {
|
|
||||||
acc.sent += curr.sent;
|
|
||||||
acc.delivered += curr.delivered;
|
|
||||||
acc.opened += curr.opened;
|
|
||||||
acc.clicked += curr.clicked;
|
|
||||||
acc.bounced += curr.bounced;
|
|
||||||
acc.complained += curr.complained;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sent: 0,
|
|
||||||
delivered: 0,
|
|
||||||
opened: 0,
|
|
||||||
clicked: 0,
|
|
||||||
bounced: 0,
|
|
||||||
complained: 0,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return { result: filledResult, totalCounts };
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
reputationMetricsData: teamProcedure
|
reputationMetricsData: teamProcedure
|
||||||
@@ -107,34 +26,8 @@ export const dashboardRouter = createTRPCRouter({
|
|||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { team } = ctx;
|
const { team } = ctx;
|
||||||
|
const response = await reputationMetricsData({team, domain: input.domain})
|
||||||
|
|
||||||
const reputations = await db.cumulatedMetrics.findMany({
|
return response;
|
||||||
where: {
|
|
||||||
teamId: team.id,
|
|
||||||
...(input.domain ? { domainId: input.domain } : {}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const results = reputations.reduce(
|
|
||||||
(acc, curr) => {
|
|
||||||
acc.delivered += Number(curr.delivered);
|
|
||||||
acc.hardBounced += Number(curr.hardBounced);
|
|
||||||
acc.complained += Number(curr.complained);
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{ delivered: 0, hardBounced: 0, complained: 0 }
|
|
||||||
);
|
|
||||||
|
|
||||||
const resultWithRates = {
|
|
||||||
...results,
|
|
||||||
bounceRate: results.delivered
|
|
||||||
? (results.hardBounced / results.delivered) * 100
|
|
||||||
: 0,
|
|
||||||
complaintRate: results.delivered
|
|
||||||
? (results.complained / results.delivered) * 100
|
|
||||||
: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
return resultWithRates;
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
|
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||||
|
import { emailTimeSeries as emailTimeSeriesService } from "~/server/service/dashboard-service";
|
||||||
|
|
||||||
|
const route = createRoute({
|
||||||
|
method: "get",
|
||||||
|
path: "/v1/analytics/email-time-series",
|
||||||
|
request: {
|
||||||
|
query: z.object({
|
||||||
|
days: z.enum(["7", "30"]).optional().openapi({
|
||||||
|
description: "Number of days to retrieve data for (default: 30)",
|
||||||
|
example: "30",
|
||||||
|
}),
|
||||||
|
domainId: z.string().optional().openapi({
|
||||||
|
description: "Filter by domain ID",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Retrieve email time series data",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
result: z.array(
|
||||||
|
z.object({
|
||||||
|
date: z.string(),
|
||||||
|
sent: z.number().int(),
|
||||||
|
delivered: z.number().int(),
|
||||||
|
opened: z.number().int(),
|
||||||
|
clicked: z.number().int(),
|
||||||
|
bounced: z.number().int(),
|
||||||
|
complained: z.number().int(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
totalCounts: z.object({
|
||||||
|
sent: z.number().int(),
|
||||||
|
delivered: z.number().int(),
|
||||||
|
opened: z.number().int(),
|
||||||
|
clicked: z.number().int(),
|
||||||
|
bounced: z.number().int(),
|
||||||
|
complained: z.number().int(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function emailTimeSeries(app: PublicAPIApp) {
|
||||||
|
app.openapi(route, async (c) => {
|
||||||
|
const team = c.var.team;
|
||||||
|
const daysParam = c.req.query("days");
|
||||||
|
const domainIdParam = c.req.query("domainId");
|
||||||
|
|
||||||
|
const days = daysParam ? Number(daysParam) : undefined;
|
||||||
|
const domain =
|
||||||
|
team.apiKey.domainId ??
|
||||||
|
(domainIdParam ? Number(domainIdParam) : undefined);
|
||||||
|
|
||||||
|
const data = await emailTimeSeriesService({ days, domain, team });
|
||||||
|
|
||||||
|
return c.json(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emailTimeSeries;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
|
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||||
|
import { reputationMetricsData as reputationMetricsDataService } from "~/server/service/dashboard-service";
|
||||||
|
|
||||||
|
const route = createRoute({
|
||||||
|
method: "get",
|
||||||
|
path: "/v1/analytics/reputation-metrics",
|
||||||
|
request: {
|
||||||
|
query: z.object({
|
||||||
|
domainId: z.string().optional().openapi({
|
||||||
|
description: "Filter by domain ID",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Retrieve reputation metrics data",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
delivered: z.number().int(),
|
||||||
|
hardBounced: z.number().int(),
|
||||||
|
complained: z.number().int(),
|
||||||
|
bounceRate: z.number(),
|
||||||
|
complaintRate: z.number(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function reputationMetricsData(app: PublicAPIApp) {
|
||||||
|
app.openapi(route, async (c) => {
|
||||||
|
const team = c.var.team;
|
||||||
|
const domainIdParam = c.req.query("domainId");
|
||||||
|
|
||||||
|
const domain =
|
||||||
|
team.apiKey.domainId ??
|
||||||
|
(domainIdParam ? Number(domainIdParam) : undefined);
|
||||||
|
|
||||||
|
const data = await reputationMetricsDataService({ domain, team });
|
||||||
|
|
||||||
|
return c.json(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default reputationMetricsData;
|
||||||
@@ -28,9 +28,12 @@ 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 emailTimeSeries from "./api/analytics/email-time-series";
|
||||||
|
import reputationMetricsData from "./api/analytics/reputation-metrics-data";
|
||||||
import bulkAddContactsHandle from "./api/contacts/bulk-add-contacts";
|
import bulkAddContactsHandle from "./api/contacts/bulk-add-contacts";
|
||||||
import bulkDeleteContacts from "./api/contacts/bulk-delete-contacts";
|
import bulkDeleteContacts from "./api/contacts/bulk-delete-contacts";
|
||||||
|
|
||||||
|
|
||||||
export const app = getApp();
|
export const app = getApp();
|
||||||
|
|
||||||
/**Domain related APIs */
|
/**Domain related APIs */
|
||||||
@@ -74,4 +77,8 @@ pauseCampaignHandle(app);
|
|||||||
resumeCampaignHandle(app);
|
resumeCampaignHandle(app);
|
||||||
deleteCampaignHandle(app);
|
deleteCampaignHandle(app);
|
||||||
|
|
||||||
|
/**Analytics related APIs */
|
||||||
|
emailTimeSeries(app);
|
||||||
|
reputationMetricsData(app);
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import { db } from "~/server/db";
|
||||||
|
import { format, subDays } from "date-fns";
|
||||||
|
import { Prisma, Team } from "@prisma/client";
|
||||||
|
|
||||||
|
type EmailTimeSeries = {
|
||||||
|
days?: number;
|
||||||
|
domain?: number
|
||||||
|
team: Team
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function emailTimeSeries(input: EmailTimeSeries) {
|
||||||
|
const days = input.days !== 7 ? 30 : 7;
|
||||||
|
const { domain, team } = input
|
||||||
|
const startDate = new Date();
|
||||||
|
startDate.setDate(startDate.getDate() - days);
|
||||||
|
const isoStartDate = startDate.toISOString().split("T")[0];
|
||||||
|
|
||||||
|
type DailyEmailUsage = {
|
||||||
|
date: string;
|
||||||
|
sent: number;
|
||||||
|
delivered: number;
|
||||||
|
opened: number;
|
||||||
|
clicked: number;
|
||||||
|
bounced: number;
|
||||||
|
complained: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await db.$queryRaw<Array<DailyEmailUsage>>`
|
||||||
|
SELECT
|
||||||
|
date,
|
||||||
|
SUM(sent)::integer AS sent,
|
||||||
|
SUM(delivered)::integer AS delivered,
|
||||||
|
SUM(opened)::integer AS opened,
|
||||||
|
SUM(clicked)::integer AS clicked,
|
||||||
|
SUM(bounced)::integer AS bounced,
|
||||||
|
SUM(complained)::integer AS complained
|
||||||
|
FROM "DailyEmailUsage"
|
||||||
|
WHERE "teamId" = ${team.id}
|
||||||
|
AND "date" >= ${isoStartDate}
|
||||||
|
${domain ? Prisma.sql`AND "domainId" = ${domain}` : Prisma.sql``}
|
||||||
|
GROUP BY "date"
|
||||||
|
ORDER BY "date" ASC
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Fill in any missing dates with 0 values
|
||||||
|
const filledResult: DailyEmailUsage[] = [];
|
||||||
|
const endDateObj = new Date();
|
||||||
|
|
||||||
|
for (let i = days; i > -1; i--) {
|
||||||
|
const dateStr = subDays(endDateObj, i)
|
||||||
|
.toISOString()
|
||||||
|
.split("T")[0] as string;
|
||||||
|
const existingData = result.find((r) => r.date === dateStr);
|
||||||
|
|
||||||
|
if (existingData) {
|
||||||
|
filledResult.push({
|
||||||
|
...existingData,
|
||||||
|
date: format(dateStr, "MMM dd"),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
filledResult.push({
|
||||||
|
date: format(dateStr, "MMM dd"),
|
||||||
|
sent: 0,
|
||||||
|
delivered: 0,
|
||||||
|
opened: 0,
|
||||||
|
clicked: 0,
|
||||||
|
bounced: 0,
|
||||||
|
complained: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalCounts = result.reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
acc.sent += curr.sent;
|
||||||
|
acc.delivered += curr.delivered;
|
||||||
|
acc.opened += curr.opened;
|
||||||
|
acc.clicked += curr.clicked;
|
||||||
|
acc.bounced += curr.bounced;
|
||||||
|
acc.complained += curr.complained;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sent: 0,
|
||||||
|
delivered: 0,
|
||||||
|
opened: 0,
|
||||||
|
clicked: 0,
|
||||||
|
bounced: 0,
|
||||||
|
complained: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return { result: filledResult, totalCounts };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type ReputationMetricsData = {
|
||||||
|
domain?: number
|
||||||
|
team: Team
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function reputationMetricsData(input: ReputationMetricsData) {
|
||||||
|
const { domain, team } = input
|
||||||
|
|
||||||
|
const reputations = await db.cumulatedMetrics.findMany({
|
||||||
|
where: {
|
||||||
|
teamId: team.id,
|
||||||
|
...(domain ? { domainId: domain } : {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = reputations.reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
acc.delivered += Number(curr.delivered);
|
||||||
|
acc.hardBounced += Number(curr.hardBounced);
|
||||||
|
acc.complained += Number(curr.complained);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ delivered: 0, hardBounced: 0, complained: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultWithRates = {
|
||||||
|
...results,
|
||||||
|
bounceRate: results.delivered
|
||||||
|
? (results.hardBounced / results.delivered) * 100
|
||||||
|
: 0,
|
||||||
|
complaintRate: results.delivered
|
||||||
|
? (results.complained / results.delivered) * 100
|
||||||
|
: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
return resultWithRates;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { paths } from "../types/schema";
|
||||||
|
import { ErrorResponse } from "../types";
|
||||||
|
import { UseSend } from "./usesend";
|
||||||
|
|
||||||
|
type EmailTimeSeriesQuery =
|
||||||
|
paths["/v1/analytics/email-time-series"]["get"]["parameters"]["query"];
|
||||||
|
|
||||||
|
type EmailTimeSeriesResponseSuccess =
|
||||||
|
paths["/v1/analytics/email-time-series"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type EmailTimeSeriesResponse = {
|
||||||
|
data: EmailTimeSeriesResponseSuccess | null;
|
||||||
|
error: ErrorResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReputationMetricsQuery =
|
||||||
|
paths["/v1/analytics/reputation-metrics"]["get"]["parameters"]["query"];
|
||||||
|
|
||||||
|
type ReputationMetricsResponseSuccess =
|
||||||
|
paths["/v1/analytics/reputation-metrics"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type ReputationMetricsResponse = {
|
||||||
|
data: ReputationMetricsResponseSuccess | null;
|
||||||
|
error: ErrorResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Analytics {
|
||||||
|
constructor(private readonly usesend: UseSend) {
|
||||||
|
this.usesend = usesend;
|
||||||
|
}
|
||||||
|
|
||||||
|
async emailTimeSeries(
|
||||||
|
query?: EmailTimeSeriesQuery,
|
||||||
|
): Promise<EmailTimeSeriesResponse> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (query?.days) params.set("days", query.days);
|
||||||
|
if (query?.domainId) params.set("domainId", query.domainId);
|
||||||
|
|
||||||
|
const qs = params.toString();
|
||||||
|
const path = `/analytics/email-time-series${qs ? `?${qs}` : ""}`;
|
||||||
|
|
||||||
|
return this.usesend.get<EmailTimeSeriesResponseSuccess>(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async reputationMetrics(
|
||||||
|
query?: ReputationMetricsQuery,
|
||||||
|
): Promise<ReputationMetricsResponse> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (query?.domainId) params.set("domainId", query.domainId);
|
||||||
|
|
||||||
|
const qs = params.toString();
|
||||||
|
const path = `/analytics/reputation-metrics${qs ? `?${qs}` : ""}`;
|
||||||
|
|
||||||
|
return this.usesend.get<ReputationMetricsResponseSuccess>(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ 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";
|
||||||
|
import { Analytics } from "./analytics";
|
||||||
import { Webhooks } from "./webhooks";
|
import { Webhooks } from "./webhooks";
|
||||||
|
|
||||||
const defaultBaseUrl = "https://app.usesend.com";
|
const defaultBaseUrl = "https://app.usesend.com";
|
||||||
@@ -26,6 +27,7 @@ export class UseSend {
|
|||||||
readonly contacts = new Contacts(this);
|
readonly contacts = new Contacts(this);
|
||||||
readonly contactBooks = new ContactBooks(this);
|
readonly contactBooks = new ContactBooks(this);
|
||||||
readonly campaigns = new Campaigns(this);
|
readonly campaigns = new Campaigns(this);
|
||||||
|
readonly analytics = new Analytics(this);
|
||||||
url = baseUrl;
|
url = baseUrl;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
Vendored
+102
@@ -1715,6 +1715,108 @@ export interface paths {
|
|||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/v1/analytics/email-time-series": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
/** @description Number of days to retrieve data for (default: 30) */
|
||||||
|
days?: "7" | "30";
|
||||||
|
/** @description Filter by domain ID */
|
||||||
|
domainId?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Retrieve email time series data */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
result: {
|
||||||
|
date: string;
|
||||||
|
sent: number;
|
||||||
|
delivered: number;
|
||||||
|
opened: number;
|
||||||
|
clicked: number;
|
||||||
|
bounced: number;
|
||||||
|
complained: number;
|
||||||
|
}[];
|
||||||
|
totalCounts: {
|
||||||
|
sent: number;
|
||||||
|
delivered: number;
|
||||||
|
opened: number;
|
||||||
|
clicked: number;
|
||||||
|
bounced: number;
|
||||||
|
complained: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
|
"/v1/analytics/reputation-metrics": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
/** @description Filter by domain ID */
|
||||||
|
domainId?: string;
|
||||||
|
};
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Retrieve reputation metrics data */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
delivered: number;
|
||||||
|
hardBounced: number;
|
||||||
|
complained: number;
|
||||||
|
bounceRate: number;
|
||||||
|
complaintRate: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
put?: never;
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export type webhooks = Record<string, never>;
|
export type webhooks = Record<string, never>;
|
||||||
export interface components {
|
export interface components {
|
||||||
|
|||||||
Reference in New Issue
Block a user