Skip to content

Commit 2bedc77

Browse files
Chigalamatt-aitkenericallam
authored
feature: initial work for sendgrid integration (#264)
* feature: initial work for sendgrid integration * fix: fixed the ts-error and added sendgrid integration to the job-catalogue * SendGrid added to dependencies * Made the from email an env var so it can be tested by non-Trigger.dev team members * Refactor: Modify sendEmail function to return void and remove SendEmailResponse type Co-authored-by: Matt Aitken <[email protected]> * Create funny-kangaroos-glow.md --------- Co-authored-by: Matt Aitken <[email protected]> Co-authored-by: Eric Allam <[email protected]>
1 parent a5b5912 commit 2bedc77

File tree

12 files changed

+559
-1202
lines changed

12 files changed

+559
-1202
lines changed

.changeset/funny-kangaroos-glow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/sendgrid": patch
3+
---
4+
5+
feature: initial work for sendgrid integration

examples/job-catalog/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"private": true,
55
"scripts": {
66
"stripe": "nodemon --watch src/stripe.ts -r tsconfig-paths/register -r dotenv/config src/stripe.ts",
7+
"sendgrid": "nodemon --watch src/sendgrid.ts -r tsconfig-paths/register -r dotenv/config src/sendgrid.ts",
78
"supabase": "nodemon --watch src/supabase.ts -r tsconfig-paths/register -r dotenv/config src/supabase.ts",
89
"supabase:types": "npx supabase gen types typescript --project-id $SUPABASE_PROJECT_ID --schema public --schema auth --schema storage > src/supabase-types.ts",
910
"dev:trigger": "trigger-cli dev --port 8080"
@@ -14,6 +15,7 @@
1415
"@trigger.dev/openai": "workspace:*",
1516
"@trigger.dev/plain": "workspace:*",
1617
"@trigger.dev/resend": "workspace:*",
18+
"@trigger.dev/sendgrid": "workspace:*",
1719
"@trigger.dev/sdk": "workspace:*",
1820
"@trigger.dev/slack": "workspace:*",
1921
"@trigger.dev/stripe": "workspace:*",
@@ -35,4 +37,4 @@
3537
"ts-node": "^10.9.1",
3638
"tsconfig-paths": "^3.14.1"
3739
}
38-
}
40+
}

examples/job-catalog/src/sendgrid.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { TriggerClient, eventTrigger } from "@trigger.dev/sdk";
2+
import { createExpressServer } from "@trigger.dev/express";
3+
import { z } from "zod";
4+
import { SendGrid } from "@trigger.dev/sendgrid";
5+
6+
export const client = new TriggerClient({
7+
id: "job-catalog",
8+
apiKey: process.env["TRIGGER_API_KEY"],
9+
verbose: false,
10+
ioLogLocalEnabled: true,
11+
});
12+
13+
const sendgrid = new SendGrid({
14+
id: "sendgrid-client",
15+
apiKey: process.env.SENDGRID_API_KEY!,
16+
});
17+
18+
client.defineJob({
19+
id: "send-Sendgrid-email",
20+
name: "Send Sendgrid Email",
21+
version: "0.1.0",
22+
trigger: eventTrigger({
23+
name: "send.email",
24+
schema: z.object({
25+
to: z.union([z.string(), z.array(z.string())]),
26+
subject: z.string(),
27+
text: z.string(),
28+
}),
29+
}),
30+
integrations: {
31+
sendgrid,
32+
},
33+
run: async (payload, io, ctx) => {
34+
await io.sendgrid.sendEmail("📧", {
35+
to: payload.to,
36+
subject: payload.subject,
37+
text: payload.text,
38+
//this email must be verified in SendGrid, otherwise you'll get a forbidden error
39+
from: process.env.SENDGRID_FROM_EMAIL!,
40+
});
41+
},
42+
});
43+
44+
createExpressServer(client);

examples/job-catalog/tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
"@trigger.dev/supabase": ["../../integrations/supabase/src/index"],
3030
"@trigger.dev/supabase/*": ["../../integrations/supabase/src/*"],
3131
"@trigger.dev/stripe": ["../../integrations/stripe/src/index"],
32-
"@trigger.dev/stripe/*": ["../../integrations/stripe/src/*"]
32+
"@trigger.dev/stripe/*": ["../../integrations/stripe/src/*"],
33+
"@trigger.dev/sendgrid": ["../../integrations/sendgrid/src/index"],
34+
"@trigger.dev/sendgrid/*": ["../../integrations/sendgrid/src/*"]
3335
}
3436
}
3537
}

examples/nextjs-example/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"typescript": "5.0.4",
3131
"zod": "3.21.4",
3232
"@trigger.dev/supabase": "workspace:*",
33-
"@trigger.dev/stripe": "workspace:*"
33+
"@trigger.dev/stripe": "workspace:*",
34+
"@trigger.dev/sendgrid": "workspace:*"
3435
},
3536
"devDependencies": {
3637
"@trigger.dev/cli": "workspace:*",
@@ -43,4 +44,4 @@
4344
"trigger.dev": {
4445
"endpointId": "nextjs-example"
4546
}
46-
}
47+
}

examples/nextjs-example/tsconfig.json

Lines changed: 96 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{
22
"compilerOptions": {
33
"target": "ES2015",
4-
"lib": ["dom", "dom.iterable", "ES2015"],
4+
"lib": [
5+
"dom",
6+
"dom.iterable",
7+
"ES2015"
8+
],
59
"allowJs": true,
610
"skipLibCheck": true,
711
"strict": true,
@@ -15,38 +19,101 @@
1519
"jsx": "preserve",
1620
"incremental": true,
1721
"paths": {
18-
"@/*": ["./src/*"],
19-
"@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"],
20-
"@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"],
21-
"@trigger.dev/nextjs": ["../../packages/nextjs/src/index"],
22-
"@trigger.dev/nextjs/*": ["../../packages/nextjs/src/*"],
23-
"@trigger.dev/core": ["../../packages/core/src/index"],
24-
"@trigger.dev/core/*": ["../../packages/core/src/*"],
25-
"@trigger.dev/integration-kit": ["../../packages/integration-kit/src/index"],
26-
"@trigger.dev/integration-kit/*": ["../../packages/integration-kit/src/*"],
27-
"@trigger.dev/github": ["../../integrations/github/src/index"],
28-
"@trigger.dev/github/*": ["../../integrations/github/src/*"],
29-
"@trigger.dev/slack": ["../../integrations/slack/src/index"],
30-
"@trigger.dev/slack/*": ["../../integrations/slack/src/*"],
31-
"@trigger.dev/openai": ["../../integrations/openai/src/index"],
32-
"@trigger.dev/openai/*": ["../../integrations/openai/src/*"],
33-
"@trigger.dev/resend": ["../../integrations/resend/src/index"],
34-
"@trigger.dev/resend/*": ["../../integrations/resend/src/*"],
35-
"@trigger.dev/typeform": ["../../integrations/typeform/src/index"],
36-
"@trigger.dev/typeform/*": ["../../integrations/typeform/src/*"],
37-
"@trigger.dev/plain": ["../../integrations/plain/src/index"],
38-
"@trigger.dev/plain/*": ["../../integrations/plain/src/*"],
39-
"@trigger.dev/supabase": ["../../integrations/supabase/src/index"],
40-
"@trigger.dev/supabase/*": ["../../integrations/supabase/src/*"],
41-
"@trigger.dev/stripe": ["../../integrations/stripe/src/index"],
42-
"@trigger.dev/stripe/*": ["../../integrations/stripe/src/*"]
22+
"@/*": [
23+
"./src/*"
24+
],
25+
"@trigger.dev/sdk": [
26+
"../../packages/trigger-sdk/src/index"
27+
],
28+
"@trigger.dev/sdk/*": [
29+
"../../packages/trigger-sdk/src/*"
30+
],
31+
"@trigger.dev/nextjs": [
32+
"../../packages/nextjs/src/index"
33+
],
34+
"@trigger.dev/nextjs/*": [
35+
"../../packages/nextjs/src/*"
36+
],
37+
"@trigger.dev/core": [
38+
"../../packages/core/src/index"
39+
],
40+
"@trigger.dev/core/*": [
41+
"../../packages/core/src/*"
42+
],
43+
"@trigger.dev/integration-kit": [
44+
"../../packages/integration-kit/src/index"
45+
],
46+
"@trigger.dev/integration-kit/*": [
47+
"../../packages/integration-kit/src/*"
48+
],
49+
"@trigger.dev/github": [
50+
"../../integrations/github/src/index"
51+
],
52+
"@trigger.dev/github/*": [
53+
"../../integrations/github/src/*"
54+
],
55+
"@trigger.dev/slack": [
56+
"../../integrations/slack/src/index"
57+
],
58+
"@trigger.dev/slack/*": [
59+
"../../integrations/slack/src/*"
60+
],
61+
"@trigger.dev/openai": [
62+
"../../integrations/openai/src/index"
63+
],
64+
"@trigger.dev/openai/*": [
65+
"../../integrations/openai/src/*"
66+
],
67+
"@trigger.dev/resend": [
68+
"../../integrations/resend/src/index"
69+
],
70+
"@trigger.dev/resend/*": [
71+
"../../integrations/resend/src/*"
72+
],
73+
"@trigger.dev/typeform": [
74+
"../../integrations/typeform/src/index"
75+
],
76+
"@trigger.dev/typeform/*": [
77+
"../../integrations/typeform/src/*"
78+
],
79+
"@trigger.dev/plain": [
80+
"../../integrations/plain/src/index"
81+
],
82+
"@trigger.dev/plain/*": [
83+
"../../integrations/plain/src/*"
84+
],
85+
"@trigger.dev/supabase": [
86+
"../../integrations/supabase/src/index"
87+
],
88+
"@trigger.dev/supabase/*": [
89+
"../../integrations/supabase/src/*"
90+
],
91+
"@trigger.dev/stripe": [
92+
"../../integrations/stripe/src/index"
93+
],
94+
"@trigger.dev/stripe/*": [
95+
"../../integrations/stripe/src/*"
96+
],
97+
"@trigger.dev/sendgrid": [
98+
"../../integrations/sendgrid/src/index"
99+
],
100+
"@trigger.dev/sendgrid/*": [
101+
"../../integrations/sendgrid/src/*"
102+
]
43103
},
44104
"plugins": [
45105
{
46106
"name": "next"
47107
}
48108
]
49109
},
50-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
51-
"exclude": ["node_modules"]
52-
}
110+
"include": [
111+
"next-env.d.ts",
112+
"**/*.ts",
113+
"**/*.tsx",
114+
".next/types/**/*.ts"
115+
],
116+
"exclude": [
117+
"node_modules"
118+
]
119+
}

integrations/sendgrid/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
# @trigger.dev/sendgrid
3+

integrations/sendgrid/package.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "@trigger.dev/sendgrid",
3+
"version": "0.0.1",
4+
"description": "Trigger.dev integration for @sendgrid/mail",
5+
"main": "./dist/index.js",
6+
"types": "./dist/index.d.ts",
7+
"publishConfig": {
8+
"access": "public"
9+
},
10+
"files": [
11+
"dist/index.js",
12+
"dist/index.d.ts",
13+
"dist/index.js.map"
14+
],
15+
"devDependencies": {
16+
"@types/node": "16.x",
17+
"rimraf": "^3.0.2",
18+
"tsup": "7.1.x",
19+
"typescript": "4.9.4"
20+
},
21+
"scripts": {
22+
"clean": "rimraf dist",
23+
"build": "npm run clean && npm run build:tsup",
24+
"build:tsup": "tsup",
25+
"typecheck": "tsc --noEmit"
26+
},
27+
"dependencies": {
28+
"@sendgrid/mail": "^7.7.0",
29+
"@trigger.dev/sdk": "workspace:^2.0.3",
30+
"@trigger.dev/integration-kit": "workspace:^2.0.3"
31+
},
32+
"engines": {
33+
"node": ">=16.8.0"
34+
}
35+
}

integrations/sendgrid/src/index.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { IntegrationClient, TriggerIntegration } from "@trigger.dev/sdk";
2+
import sgMail from "@sendgrid/mail";
3+
4+
import type { AuthenticatedTask } from "@trigger.dev/sdk";
5+
6+
type SendEmailData = Parameters<(typeof sgMail)["send"]>[0];
7+
export const sendEmail: AuthenticatedTask<typeof sgMail, SendEmailData, void> = {
8+
run: async (params, client) => {
9+
await client.send(params);
10+
},
11+
init: (params) => {
12+
const subjectProperty = Array.isArray(params)
13+
? []
14+
: params.subject
15+
? [{ label: "Subject", text: params.subject }]
16+
: [];
17+
18+
return {
19+
name: "Send Email",
20+
params,
21+
icon: "sendgrid",
22+
properties: [
23+
{
24+
label: "From",
25+
text: Array.isArray(params)
26+
? getEmailFromEmailData(params[0].from)
27+
: getEmailFromEmailData(params.from),
28+
},
29+
{
30+
label: "To",
31+
text: Array.isArray(params)
32+
? getEmailFromEmailData(params[0]?.to)
33+
: getEmailFromEmailData(params?.to),
34+
},
35+
...subjectProperty,
36+
],
37+
retry: {
38+
limit: 8,
39+
factor: 1.8,
40+
minTimeoutInMs: 500,
41+
maxTimeoutInMs: 30000,
42+
randomize: true,
43+
},
44+
};
45+
},
46+
};
47+
48+
export const tasks = {
49+
sendEmail,
50+
};
51+
52+
export type SendGridIntegrationOptions = {
53+
id: string;
54+
apiKey: string;
55+
};
56+
57+
export class SendGrid
58+
implements TriggerIntegration<IntegrationClient<typeof sgMail, typeof tasks>>
59+
{
60+
client: IntegrationClient<typeof sgMail, typeof tasks>;
61+
62+
constructor(private options: SendGridIntegrationOptions) {
63+
if (!options.apiKey) {
64+
throw new Error(`Can't create SendGrid integration (${options.id}) as apiKey was undefined`);
65+
}
66+
67+
sgMail.setApiKey(options.apiKey);
68+
69+
this.client = {
70+
tasks,
71+
usesLocalAuth: true,
72+
client: sgMail,
73+
auth: {
74+
apiKey: options.apiKey,
75+
},
76+
};
77+
}
78+
79+
get id() {
80+
return this.options.id;
81+
}
82+
83+
get metadata() {
84+
return { id: "sendgrid", name: "SendGrid" };
85+
}
86+
}
87+
88+
type EmailData = string | { name?: string; email: string };
89+
type EmailDataArray = EmailData | EmailData[];
90+
91+
function getEmailFromEmailData(emailData: EmailDataArray | undefined): string {
92+
if (emailData === undefined) {
93+
return "";
94+
}
95+
if (Array.isArray(emailData)) {
96+
return emailData.map(getEmailFromEmailData).join(", ");
97+
}
98+
99+
if (typeof emailData === "string") {
100+
return emailData;
101+
}
102+
return emailData.email;
103+
}

0 commit comments

Comments
 (0)