Skip to content

Commit 3fe96b4

Browse files
authored
add batch email api (#149)
* add bulk email * add bulk email api * add batch email sdk changes
1 parent 44e4f43 commit 3fe96b4

File tree

10 files changed

+724
-49
lines changed

10 files changed

+724
-49
lines changed

apps/docs/api-reference/openapi.json

Lines changed: 202 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,10 @@
243243
{
244244
"schema": {
245245
"type": "number",
246+
"nullable": true,
246247
"example": 1
247248
},
248-
"required": true,
249+
"required": false,
249250
"name": "id",
250251
"in": "path"
251252
}
@@ -494,21 +495,25 @@
494495
"to": {
495496
"anyOf": [
496497
{
497-
"type": "string"
498+
"type": "string",
499+
"format": "email"
498500
},
499501
{
500502
"type": "array",
501503
"items": {
502-
"type": "string"
504+
"type": "string",
505+
"format": "email"
503506
}
504507
}
505508
]
506509
},
507510
"from": {
508-
"type": "string"
511+
"type": "string",
512+
"format": "email"
509513
},
510514
"subject": {
511515
"type": "string",
516+
"minLength": 1,
512517
"description": "Optional when templateId is provided"
513518
},
514519
"templateId": {
@@ -524,65 +529,78 @@
524529
"replyTo": {
525530
"anyOf": [
526531
{
527-
"type": "string"
532+
"type": "string",
533+
"format": "email"
528534
},
529535
{
530536
"type": "array",
531537
"items": {
532-
"type": "string"
538+
"type": "string",
539+
"format": "email"
533540
}
534541
}
535542
]
536543
},
537544
"cc": {
538545
"anyOf": [
539546
{
540-
"type": "string"
547+
"type": "string",
548+
"format": "email"
541549
},
542550
{
543551
"type": "array",
544552
"items": {
545-
"type": "string"
553+
"type": "string",
554+
"format": "email"
546555
}
547556
}
548557
]
549558
},
550559
"bcc": {
551560
"anyOf": [
552561
{
553-
"type": "string"
562+
"type": "string",
563+
"format": "email"
554564
},
555565
{
556566
"type": "array",
557567
"items": {
558-
"type": "string"
568+
"type": "string",
569+
"format": "email"
559570
}
560571
}
561572
]
562573
},
563574
"text": {
564-
"type": "string"
575+
"type": "string",
576+
"nullable": true,
577+
"minLength": 1
565578
},
566579
"html": {
567-
"type": "string"
580+
"type": "string",
581+
"nullable": true,
582+
"minLength": 1
568583
},
569584
"attachments": {
570585
"type": "array",
571586
"items": {
572587
"type": "object",
573588
"properties": {
574589
"filename": {
575-
"type": "string"
590+
"type": "string",
591+
"minLength": 1
576592
},
577593
"content": {
578-
"type": "string"
594+
"type": "string",
595+
"minLength": 1
579596
}
580597
},
581598
"required": [
582599
"filename",
583600
"content"
584601
]
585-
}
602+
},
603+
"maxItems": 10
586604
},
587605
"scheduledAt": {
588606
"type": "string",
@@ -616,6 +634,175 @@
616634
}
617635
}
618636
},
637+
"/v1/emails/batch": {
638+
"post": {
639+
"requestBody": {
640+
"required": true,
641+
"content": {
642+
"application/json": {
643+
"schema": {
644+
"type": "array",
645+
"items": {
646+
"type": "object",
647+
"properties": {
648+
"to": {
649+
"anyOf": [
650+
{
651+
"type": "string",
652+
"format": "email"
653+
},
654+
{
655+
"type": "array",
656+
"items": {
657+
"type": "string",
658+
"format": "email"
659+
}
660+
}
661+
]
662+
},
663+
"from": {
664+
"type": "string",
665+
"format": "email"
666+
},
667+
"subject": {
668+
"type": "string",
669+
"minLength": 1,
670+
"description": "Optional when templateId is provided"
671+
},
672+
"templateId": {
673+
"type": "string",
674+
"description": "ID of a template from the dashboard"
675+
},
676+
"variables": {
677+
"type": "object",
678+
"additionalProperties": {
679+
"type": "string"
680+
}
681+
},
682+
"replyTo": {
683+
"anyOf": [
684+
{
685+
"type": "string",
686+
"format": "email"
687+
},
688+
{
689+
"type": "array",
690+
"items": {
691+
"type": "string",
692+
"format": "email"
693+
}
694+
}
695+
]
696+
},
697+
"cc": {
698+
"anyOf": [
699+
{
700+
"type": "string",
701+
"format": "email"
702+
},
703+
{
704+
"type": "array",
705+
"items": {
706+
"type": "string",
707+
"format": "email"
708+
}
709+
}
710+
]
711+
},
712+
"bcc": {
713+
"anyOf": [
714+
{
715+
"type": "string",
716+
"format": "email"
717+
},
718+
{
719+
"type": "array",
720+
"items": {
721+
"type": "string",
722+
"format": "email"
723+
}
724+
}
725+
]
726+
},
727+
"text": {
728+
"type": "string",
729+
"nullable": true,
730+
"minLength": 1
731+
},
732+
"html": {
733+
"type": "string",
734+
"nullable": true,
735+
"minLength": 1
736+
},
737+
"attachments": {
738+
"type": "array",
739+
"items": {
740+
"type": "object",
741+
"properties": {
742+
"filename": {
743+
"type": "string",
744+
"minLength": 1
745+
},
746+
"content": {
747+
"type": "string",
748+
"minLength": 1
749+
}
750+
},
751+
"required": [
752+
"filename",
753+
"content"
754+
]
755+
},
756+
"maxItems": 10
757+
},
758+
"scheduledAt": {
759+
"type": "string",
760+
"format": "date-time"
761+
}
762+
},
763+
"required": [
764+
"to",
765+
"from"
766+
]
767+
},
768+
"maxItems": 100
769+
}
770+
}
771+
}
772+
},
773+
"responses": {
774+
"200": {
775+
"description": "List of successfully created email IDs",
776+
"content": {
777+
"application/json": {
778+
"schema": {
779+
"type": "object",
780+
"properties": {
781+
"data": {
782+
"type": "array",
783+
"items": {
784+
"type": "object",
785+
"properties": {
786+
"emailId": {
787+
"type": "string"
788+
}
789+
},
790+
"required": [
791+
"emailId"
792+
]
793+
}
794+
}
795+
},
796+
"required": [
797+
"data"
798+
]
799+
}
800+
}
801+
}
802+
}
803+
}
804+
}
805+
},
619806
"/v1/emails/{emailId}/cancel": {
620807
"post": {
621808
"parameters": [
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { createRoute, z } from "@hono/zod-openapi";
2+
import { PublicAPIApp } from "~/server/public-api/hono";
3+
import { getTeamFromToken } from "~/server/public-api/auth";
4+
import { sendBulkEmails } from "~/server/service/email-service";
5+
import { EmailContent } from "~/types";
6+
import { emailSchema } from "../../schemas/email-schema"; // Corrected import path
7+
8+
// Define the schema for a single email within the bulk request
9+
// This is similar to the schema in send-email.ts but without the top-level 'required'
10+
// Removed inline emailSchema definition
11+
12+
const route = createRoute({
13+
method: "post",
14+
path: "/v1/emails/batch",
15+
request: {
16+
body: {
17+
required: true,
18+
content: {
19+
"application/json": {
20+
// Use the imported schema in an array
21+
schema: z.array(emailSchema).max(100, {
22+
message:
23+
"Cannot send more than 100 emails in a single bulk request",
24+
}),
25+
},
26+
},
27+
},
28+
},
29+
responses: {
30+
200: {
31+
content: {
32+
"application/json": {
33+
// Return an array of objects with the created email IDs
34+
schema: z.object({
35+
data: z.array(z.object({ emailId: z.string() })),
36+
}),
37+
},
38+
},
39+
description: "List of successfully created email IDs",
40+
},
41+
// Add other potential error responses based on sendBulkEmails logic if needed
42+
},
43+
});
44+
45+
function sendBatch(app: PublicAPIApp) {
46+
app.openapi(route, async (c) => {
47+
const team = await getTeamFromToken(c);
48+
const emailPayloads = c.req.valid("json");
49+
50+
// Add teamId and apiKeyId to each email payload
51+
const emailsToSend: Array<
52+
EmailContent & { teamId: number; apiKeyId?: number }
53+
> = emailPayloads.map((payload) => ({
54+
...payload,
55+
text: payload.text ?? undefined,
56+
html: payload.html ?? undefined,
57+
teamId: team.id,
58+
apiKeyId: team.apiKeyId,
59+
}));
60+
61+
// Call the service function to send emails in bulk
62+
const createdEmails = await sendBulkEmails(emailsToSend);
63+
64+
// Map the result to the response format
65+
const responseData = createdEmails.map((email) => ({
66+
emailId: email.id,
67+
}));
68+
69+
return c.json({ data: responseData });
70+
});
71+
}
72+
73+
export default sendBatch;

0 commit comments

Comments
 (0)