feat: add list emails api (#167)
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
---
|
---
|
||||||
openapi: get /v1/contactBooks/{contactBookId}/contacts/
|
openapi: get /v1/contactBooks/{contactBookId}/contacts
|
||||||
---
|
---
|
||||||
|
3
apps/docs/api-reference/emails/list-emails.mdx
Normal file
3
apps/docs/api-reference/emails/list-emails.mdx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
openapi: get /v1/emails
|
||||||
|
---
|
@@ -484,6 +484,223 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/emails": {
|
"/v1/emails": {
|
||||||
|
"get": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "1",
|
||||||
|
"example": "1"
|
||||||
|
},
|
||||||
|
"required": false,
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "50",
|
||||||
|
"example": "50"
|
||||||
|
},
|
||||||
|
"required": false,
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2024-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
|
"required": false,
|
||||||
|
"name": "startDate",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2024-01-31T23:59:59Z"
|
||||||
|
},
|
||||||
|
"required": false,
|
||||||
|
"name": "endDate",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"schema": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "123"
|
||||||
|
},
|
||||||
|
"required": false,
|
||||||
|
"name": "domainId",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Retrieve a list of emails",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"replyTo": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cc": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bcc": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"from": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"subject": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"html": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"latestStatus": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"enum": [
|
||||||
|
"SCHEDULED",
|
||||||
|
"QUEUED",
|
||||||
|
"SENT",
|
||||||
|
"DELIVERY_DELAYED",
|
||||||
|
"BOUNCED",
|
||||||
|
"REJECTED",
|
||||||
|
"RENDERING_FAILURE",
|
||||||
|
"DELIVERED",
|
||||||
|
"OPENED",
|
||||||
|
"CLICKED",
|
||||||
|
"COMPLAINED",
|
||||||
|
"FAILED",
|
||||||
|
"CANCELLED"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scheduledAt": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"domainId": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"to",
|
||||||
|
"from",
|
||||||
|
"subject",
|
||||||
|
"html",
|
||||||
|
"text",
|
||||||
|
"createdAt",
|
||||||
|
"updatedAt",
|
||||||
|
"latestStatus",
|
||||||
|
"scheduledAt",
|
||||||
|
"domainId"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"count": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"data",
|
||||||
|
"count"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -495,21 +712,18 @@
|
|||||||
"to": {
|
"to": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"format": "email"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"format": "email"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"from": {
|
"from": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"format": "email"
|
|
||||||
},
|
},
|
||||||
"subject": {
|
"subject": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -652,21 +866,18 @@
|
|||||||
"to": {
|
"to": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"format": "email"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"format": "email"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"from": {
|
"from": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"format": "email"
|
|
||||||
},
|
},
|
||||||
"subject": {
|
"subject": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@@ -77,6 +77,7 @@
|
|||||||
"group": "Emails",
|
"group": "Emails",
|
||||||
"pages": [
|
"pages": [
|
||||||
"api-reference/emails/get-email",
|
"api-reference/emails/get-email",
|
||||||
|
"api-reference/emails/list-emails",
|
||||||
"api-reference/emails/send-email",
|
"api-reference/emails/send-email",
|
||||||
"api-reference/emails/batch-email",
|
"api-reference/emails/batch-email",
|
||||||
"api-reference/emails/update-schedule",
|
"api-reference/emails/update-schedule",
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
export const DEFAULT_QUERY_LIMIT = 30;
|
export const DEFAULT_QUERY_LIMIT = 50;
|
||||||
|
|
||||||
/* Reputation constants */
|
/* Reputation constants */
|
||||||
export const HARD_BOUNCE_WARNING_RATE = 5;
|
export const HARD_BOUNCE_WARNING_RATE = 5;
|
||||||
|
170
apps/web/src/server/public-api/api/emails/list-emails.ts
Normal file
170
apps/web/src/server/public-api/api/emails/list-emails.ts
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
|
import { PublicAPIApp } from "~/server/public-api/hono";
|
||||||
|
import { db } from "~/server/db";
|
||||||
|
import { EmailStatus, Prisma } from "@prisma/client";
|
||||||
|
import { DEFAULT_QUERY_LIMIT } from "~/lib/constants";
|
||||||
|
|
||||||
|
const EmailSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
to: z.string().or(z.array(z.string())),
|
||||||
|
replyTo: z.string().or(z.array(z.string())).optional().nullable(),
|
||||||
|
cc: z.string().or(z.array(z.string())).optional().nullable(),
|
||||||
|
bcc: z.string().or(z.array(z.string())).optional().nullable(),
|
||||||
|
from: z.string(),
|
||||||
|
subject: z.string(),
|
||||||
|
html: z.string().nullable(),
|
||||||
|
text: z.string().nullable(),
|
||||||
|
createdAt: z.string(),
|
||||||
|
updatedAt: z.string(),
|
||||||
|
latestStatus: z.nativeEnum(EmailStatus).nullable(),
|
||||||
|
scheduledAt: z.string().datetime().nullable(),
|
||||||
|
domainId: z.number().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = createRoute({
|
||||||
|
method: "get",
|
||||||
|
path: "/v1/emails",
|
||||||
|
request: {
|
||||||
|
query: z.object({
|
||||||
|
page: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default("1")
|
||||||
|
.transform(Number)
|
||||||
|
.openapi({
|
||||||
|
param: {
|
||||||
|
name: "page",
|
||||||
|
in: "query",
|
||||||
|
},
|
||||||
|
example: "1",
|
||||||
|
}),
|
||||||
|
limit: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default(String(DEFAULT_QUERY_LIMIT))
|
||||||
|
.pipe(z.coerce.number().min(1).max(DEFAULT_QUERY_LIMIT))
|
||||||
|
.openapi({
|
||||||
|
param: {
|
||||||
|
name: "limit",
|
||||||
|
in: "query",
|
||||||
|
},
|
||||||
|
example: String(DEFAULT_QUERY_LIMIT),
|
||||||
|
}),
|
||||||
|
startDate: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.optional()
|
||||||
|
.openapi({
|
||||||
|
param: {
|
||||||
|
name: "startDate",
|
||||||
|
in: "query",
|
||||||
|
},
|
||||||
|
example: "2024-01-01T00:00:00Z",
|
||||||
|
}),
|
||||||
|
endDate: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.optional()
|
||||||
|
.openapi({
|
||||||
|
param: {
|
||||||
|
name: "endDate",
|
||||||
|
in: "query",
|
||||||
|
},
|
||||||
|
example: "2024-01-31T23:59:59Z",
|
||||||
|
}),
|
||||||
|
domainId: z
|
||||||
|
.union([z.string(), z.array(z.string())])
|
||||||
|
.optional()
|
||||||
|
.transform((val) => {
|
||||||
|
if (!val) return undefined;
|
||||||
|
return (Array.isArray(val) ? val : [val]).map(Number);
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
param: {
|
||||||
|
name: "domainId",
|
||||||
|
in: "query",
|
||||||
|
},
|
||||||
|
example: "123", // or ["123", "456"]
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.array(EmailSchema),
|
||||||
|
count: z.number(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Retrieve a list of emails",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function listEmails(app: PublicAPIApp) {
|
||||||
|
app.openapi(route, async (c) => {
|
||||||
|
const team = c.var.team;
|
||||||
|
const { page, limit, startDate, endDate, domainId } = c.req.valid("query");
|
||||||
|
|
||||||
|
const whereClause: Prisma.EmailWhereInput = {
|
||||||
|
teamId: team.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (startDate) {
|
||||||
|
whereClause.createdAt = {
|
||||||
|
gte: new Date(startDate),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (endDate) {
|
||||||
|
whereClause.createdAt = {
|
||||||
|
lte: new Date(endDate),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (domainId && domainId.length > 0) {
|
||||||
|
whereClause.domainId = { in: domainId };
|
||||||
|
}
|
||||||
|
|
||||||
|
const [emails, count] = await db.$transaction([
|
||||||
|
db.email.findMany({
|
||||||
|
where: whereClause,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
to: true,
|
||||||
|
replyTo: true,
|
||||||
|
cc: true,
|
||||||
|
bcc: true,
|
||||||
|
from: true,
|
||||||
|
subject: true,
|
||||||
|
html: true,
|
||||||
|
text: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
latestStatus: true,
|
||||||
|
scheduledAt: true,
|
||||||
|
domainId: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc",
|
||||||
|
},
|
||||||
|
skip: (page - 1) * limit,
|
||||||
|
take: limit,
|
||||||
|
}),
|
||||||
|
db.email.count({ where: whereClause }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
data: emails.map((email) => ({
|
||||||
|
...email,
|
||||||
|
createdAt: email.createdAt.toISOString(),
|
||||||
|
updatedAt: email.updatedAt.toISOString(),
|
||||||
|
scheduledAt: email.scheduledAt ? email.scheduledAt.toISOString() : null,
|
||||||
|
})),
|
||||||
|
count,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default listEmails;
|
@@ -2,6 +2,7 @@ import { getApp } from "./hono";
|
|||||||
import getDomains from "./api/domains/get-domains";
|
import getDomains from "./api/domains/get-domains";
|
||||||
import sendEmail from "./api/emails/send-email";
|
import sendEmail from "./api/emails/send-email";
|
||||||
import getEmail from "./api/emails/get-email";
|
import getEmail from "./api/emails/get-email";
|
||||||
|
import listEmails from "./api/emails/list-emails";
|
||||||
import addContact from "./api/contacts/add-contact";
|
import addContact from "./api/contacts/add-contact";
|
||||||
import updateContactInfo from "./api/contacts/update-contact";
|
import updateContactInfo from "./api/contacts/update-contact";
|
||||||
import getContact from "./api/contacts/get-contact";
|
import getContact from "./api/contacts/get-contact";
|
||||||
@@ -23,6 +24,7 @@ verifyDomain(app);
|
|||||||
|
|
||||||
/**Email related APIs */
|
/**Email related APIs */
|
||||||
getEmail(app);
|
getEmail(app);
|
||||||
|
listEmails(app);
|
||||||
sendEmail(app);
|
sendEmail(app);
|
||||||
sendBatch(app);
|
sendBatch(app);
|
||||||
updateEmailScheduledAt(app);
|
updateEmailScheduledAt(app);
|
||||||
|
Reference in New Issue
Block a user