mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-19 05:46:04 +00:00
init app resources
This commit is contained in:
13
app/src/core/drivers/email/index.ts
Normal file
13
app/src/core/drivers/email/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export type TEmailResponse<Data = unknown> = {
|
||||
success: boolean;
|
||||
data?: Data;
|
||||
};
|
||||
|
||||
export interface IEmailDriver<Data = unknown, Options = object> {
|
||||
send(
|
||||
to: string,
|
||||
subject: string,
|
||||
body: string | { text: string; html: string },
|
||||
options?: Options,
|
||||
): Promise<TEmailResponse<Data>>;
|
||||
}
|
||||
116
app/src/core/drivers/email/mailchannels.ts
Normal file
116
app/src/core/drivers/email/mailchannels.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { mergeObject, type RecursivePartial } from "core/utils";
|
||||
import type { IEmailDriver } from "./index";
|
||||
|
||||
export type MailchannelsEmailOptions = {
|
||||
apiKey: string;
|
||||
host?: string;
|
||||
from?: { email: string; name: string };
|
||||
};
|
||||
|
||||
export type Recipient = {
|
||||
email: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export type MailchannelsSendOptions = RecursivePartial<{
|
||||
attachments: Array<{
|
||||
content: string;
|
||||
filename: string;
|
||||
type: string;
|
||||
}>;
|
||||
campaign_id: string;
|
||||
content: Array<{
|
||||
template_type?: string;
|
||||
type: string;
|
||||
value: string;
|
||||
}>;
|
||||
dkim_domain: string;
|
||||
dkim_private_key: string;
|
||||
dkim_selector: string;
|
||||
from: Recipient;
|
||||
headers: {};
|
||||
personalizations: Array<{
|
||||
bcc: Array<Recipient>;
|
||||
cc: Array<Recipient>;
|
||||
dkim_domain: string;
|
||||
dkim_private_key: string;
|
||||
dkim_selector: string;
|
||||
dynamic_template_data: {};
|
||||
from: Recipient;
|
||||
headers: {};
|
||||
reply_to: Recipient;
|
||||
subject: string;
|
||||
to: Array<Recipient>;
|
||||
}>;
|
||||
reply_to: Recipient;
|
||||
subject: string;
|
||||
tracking_settings: {
|
||||
click_tracking: {
|
||||
enable: boolean;
|
||||
};
|
||||
open_tracking: {
|
||||
enable: boolean;
|
||||
};
|
||||
};
|
||||
transactional: boolean;
|
||||
}>;
|
||||
|
||||
export type MailchannelsEmailResponse = {
|
||||
request_id: string;
|
||||
results: Array<{
|
||||
index: number;
|
||||
message_id: string;
|
||||
reason: string;
|
||||
status: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export const mailchannelsEmail = (
|
||||
config: MailchannelsEmailOptions,
|
||||
): IEmailDriver<MailchannelsEmailResponse, MailchannelsSendOptions> => {
|
||||
const host = config.host ?? "https://api.mailchannels.net/tx/v1/send";
|
||||
const from = config.from ?? { email: "onboarding@mailchannels.net", name: "Mailchannels" };
|
||||
return {
|
||||
send: async (
|
||||
to: string,
|
||||
subject: string,
|
||||
body: string | { text: string; html: string },
|
||||
options?: MailchannelsSendOptions,
|
||||
) => {
|
||||
const payload: MailchannelsSendOptions = mergeObject(
|
||||
{
|
||||
from,
|
||||
subject,
|
||||
content:
|
||||
typeof body === "string"
|
||||
? [{ type: "text/html", value: body }]
|
||||
: [
|
||||
{ type: "text/plain", value: body.text },
|
||||
{ type: "text/html", value: body.html },
|
||||
],
|
||||
personalizations: [
|
||||
{
|
||||
to: [{ email: to }],
|
||||
},
|
||||
],
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
const res = await fetch(host, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Api-Key": config.apiKey,
|
||||
},
|
||||
body: JSON.stringify({ ...payload, ...options }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as MailchannelsEmailResponse;
|
||||
return { success: true, data };
|
||||
}
|
||||
return { success: false };
|
||||
},
|
||||
};
|
||||
};
|
||||
72
app/src/core/drivers/email/resend.ts
Normal file
72
app/src/core/drivers/email/resend.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { IEmailDriver } from "./index";
|
||||
|
||||
export type ResendEmailOptions = {
|
||||
apiKey: string;
|
||||
host?: string;
|
||||
from?: string;
|
||||
};
|
||||
|
||||
export type ResendEmailSendOptions = {
|
||||
bcc?: string | string[];
|
||||
cc?: string | string[];
|
||||
reply_to?: string | string[];
|
||||
scheduled_at?: string;
|
||||
headers?: Record<string, string>;
|
||||
attachments?: {
|
||||
content: Buffer | string;
|
||||
filename: string;
|
||||
path: string;
|
||||
content_type: string;
|
||||
}[];
|
||||
tags?: {
|
||||
name: string;
|
||||
value: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type ResendEmailResponse = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const resendEmail = (
|
||||
config: ResendEmailOptions,
|
||||
): IEmailDriver<ResendEmailResponse, ResendEmailSendOptions> => {
|
||||
const host = config.host ?? "https://api.resend.com/emails";
|
||||
const from = config.from ?? "Acme <onboarding@resend.dev>";
|
||||
return {
|
||||
send: async (
|
||||
to: string,
|
||||
subject: string,
|
||||
body: string | { text: string; html: string },
|
||||
options?: ResendEmailSendOptions,
|
||||
) => {
|
||||
const payload: any = {
|
||||
from,
|
||||
to,
|
||||
subject,
|
||||
};
|
||||
|
||||
if (typeof body === "string") {
|
||||
payload.html = body;
|
||||
} else {
|
||||
payload.html = body.html;
|
||||
payload.text = body.text;
|
||||
}
|
||||
|
||||
const res = await fetch(host, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${config.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({ ...payload, ...options }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as ResendEmailResponse;
|
||||
return { success: true, data };
|
||||
}
|
||||
return { success: false };
|
||||
},
|
||||
};
|
||||
};
|
||||
89
app/src/core/drivers/email/ses.ts
Normal file
89
app/src/core/drivers/email/ses.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { IEmailDriver } from "./index";
|
||||
import { AwsClient } from "aws4fetch";
|
||||
|
||||
export type SesEmailOptions = {
|
||||
region: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
from: string;
|
||||
};
|
||||
|
||||
export type SesSendOptions = {
|
||||
cc?: string[];
|
||||
bcc?: string[];
|
||||
replyTo?: string[];
|
||||
};
|
||||
|
||||
export type SesEmailResponse = {
|
||||
MessageId?: string;
|
||||
status: number;
|
||||
body: string;
|
||||
};
|
||||
|
||||
export const sesEmail = (
|
||||
config: SesEmailOptions,
|
||||
): IEmailDriver<SesEmailResponse, SesSendOptions> => {
|
||||
const endpoint = `https://email.${config.region}.amazonaws.com/`;
|
||||
const from = config.from;
|
||||
const aws = new AwsClient({
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
service: "ses",
|
||||
region: config.region,
|
||||
});
|
||||
return {
|
||||
send: async (
|
||||
to: string,
|
||||
subject: string,
|
||||
body: string | { text: string; html: string },
|
||||
options?: SesSendOptions,
|
||||
) => {
|
||||
// build SES SendEmail params (x-www-form-urlencoded)
|
||||
const params: Record<string, string> = {
|
||||
Action: "SendEmail",
|
||||
Version: "2010-12-01",
|
||||
Source: from,
|
||||
"Destination.ToAddresses.member.1": to,
|
||||
"Message.Subject.Data": subject,
|
||||
};
|
||||
if (typeof body === "string") {
|
||||
params["Message.Body.Html.Data"] = body;
|
||||
} else {
|
||||
params["Message.Body.Html.Data"] = body.html;
|
||||
params["Message.Body.Text.Data"] = body.text;
|
||||
}
|
||||
if (options?.cc) {
|
||||
options.cc.forEach((cc, i) => {
|
||||
params[`Destination.CcAddresses.member.${i + 1}`] = cc;
|
||||
});
|
||||
}
|
||||
if (options?.bcc) {
|
||||
options.bcc.forEach((bcc, i) => {
|
||||
params[`Destination.BccAddresses.member.${i + 1}`] = bcc;
|
||||
});
|
||||
}
|
||||
if (options?.replyTo) {
|
||||
options.replyTo.forEach((reply, i) => {
|
||||
params[`ReplyToAddresses.member.${i + 1}`] = reply;
|
||||
});
|
||||
}
|
||||
const formBody = Object.entries(params)
|
||||
.map(([k, v]) => encodeURIComponent(k) + "=" + encodeURIComponent(v))
|
||||
.join("&");
|
||||
const res = await aws.fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||
body: formBody,
|
||||
});
|
||||
const text = await res.text();
|
||||
// try to extract MessageId from XML response
|
||||
let MessageId: string | undefined = undefined;
|
||||
const match = text.match(/<MessageId>([^<]+)<\/MessageId>/);
|
||||
if (match) MessageId = match[1];
|
||||
return {
|
||||
success: res.ok,
|
||||
data: { MessageId, status: res.status, body: text },
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user