mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 21:06:04 +00:00
Merge pull request #294 from cameronapak/claude/add-loops-email-driver-011CUxMpVqe8AT22gN2k5ZVm
Email Driver: Add Plunk Support
This commit is contained in:
@@ -20,6 +20,7 @@ VITE_SHOW_ROUTES=
|
|||||||
|
|
||||||
# ===== Test Credentials =====
|
# ===== Test Credentials =====
|
||||||
RESEND_API_KEY=
|
RESEND_API_KEY=
|
||||||
|
PLUNK_API_KEY=
|
||||||
R2_TOKEN=
|
R2_TOKEN=
|
||||||
|
|
||||||
R2_ACCESS_KEY=
|
R2_ACCESS_KEY=
|
||||||
|
|||||||
55
app/src/core/drivers/email/plunk.spec.ts
Normal file
55
app/src/core/drivers/email/plunk.spec.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { describe, it, expect } from "bun:test";
|
||||||
|
import { plunkEmail } from "./plunk";
|
||||||
|
|
||||||
|
const ALL_TESTS = !!process.env.ALL_TESTS;
|
||||||
|
|
||||||
|
describe.skipIf(ALL_TESTS)("plunk", () => {
|
||||||
|
it("should throw on failed", async () => {
|
||||||
|
const driver = plunkEmail({ apiKey: "invalid" });
|
||||||
|
expect(driver.send("foo@bar.com", "Test", "Test")).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send an email", async () => {
|
||||||
|
const driver = plunkEmail({
|
||||||
|
apiKey: process.env.PLUNK_API_KEY!,
|
||||||
|
from: undefined, // Default to what Plunk sets
|
||||||
|
});
|
||||||
|
const response = await driver.send(
|
||||||
|
"help@bknd.io",
|
||||||
|
"Test Email from Plunk",
|
||||||
|
"This is a test email",
|
||||||
|
);
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.success).toBe(true);
|
||||||
|
expect(response.emails).toBeDefined();
|
||||||
|
expect(response.timestamp).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send HTML email", async () => {
|
||||||
|
const driver = plunkEmail({
|
||||||
|
apiKey: process.env.PLUNK_API_KEY!,
|
||||||
|
from: undefined,
|
||||||
|
});
|
||||||
|
const htmlBody = "<h1>Test Email</h1><p>This is a test email</p>";
|
||||||
|
const response = await driver.send(
|
||||||
|
"help@bknd.io",
|
||||||
|
"HTML Test",
|
||||||
|
htmlBody,
|
||||||
|
);
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send with text and html", async () => {
|
||||||
|
const driver = plunkEmail({
|
||||||
|
apiKey: process.env.PLUNK_API_KEY!,
|
||||||
|
from: undefined,
|
||||||
|
});
|
||||||
|
const response = await driver.send("test@example.com", "Test Email", {
|
||||||
|
text: "help@bknd.io",
|
||||||
|
html: "<p>This is HTML</p>",
|
||||||
|
});
|
||||||
|
expect(response).toBeDefined();
|
||||||
|
expect(response.success).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
70
app/src/core/drivers/email/plunk.ts
Normal file
70
app/src/core/drivers/email/plunk.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import type { IEmailDriver } from "./index";
|
||||||
|
|
||||||
|
export type PlunkEmailOptions = {
|
||||||
|
apiKey: string;
|
||||||
|
host?: string;
|
||||||
|
from?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PlunkEmailSendOptions = {
|
||||||
|
subscribed?: boolean;
|
||||||
|
name?: string;
|
||||||
|
from?: string;
|
||||||
|
reply?: string;
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PlunkEmailResponse = {
|
||||||
|
success: boolean;
|
||||||
|
emails: Array<{
|
||||||
|
contact: {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
email: string;
|
||||||
|
}>;
|
||||||
|
timestamp: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const plunkEmail = (
|
||||||
|
config: PlunkEmailOptions,
|
||||||
|
): IEmailDriver<PlunkEmailResponse, PlunkEmailSendOptions> => {
|
||||||
|
const host = config.host ?? "https://api.useplunk.com/v1/send";
|
||||||
|
const from = config.from;
|
||||||
|
|
||||||
|
return {
|
||||||
|
send: async (
|
||||||
|
to: string,
|
||||||
|
subject: string,
|
||||||
|
body: string | { text: string; html: string },
|
||||||
|
options?: PlunkEmailSendOptions,
|
||||||
|
) => {
|
||||||
|
const payload: any = {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
subject,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof body === "string") {
|
||||||
|
payload.body = body;
|
||||||
|
} else {
|
||||||
|
payload.body = body.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(host, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${config.apiKey}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ ...payload, ...options }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Plunk API error: ${await res.text()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await res.json()) as PlunkEmailResponse;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -5,3 +5,4 @@ export type { IEmailDriver } from "./email";
|
|||||||
export { resendEmail } from "./email/resend";
|
export { resendEmail } from "./email/resend";
|
||||||
export { sesEmail } from "./email/ses";
|
export { sesEmail } from "./email/ses";
|
||||||
export { mailchannelsEmail } from "./email/mailchannels";
|
export { mailchannelsEmail } from "./email/mailchannels";
|
||||||
|
export { plunkEmail } from "./email/plunk";
|
||||||
|
|||||||
Reference in New Issue
Block a user