@storecraft/mailer-providers-http 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # `storecraft` Official serverless http email providers
2
+
3
+ Supports wellknown http-based `serverless` friendly `email` providers,
4
+
5
+ - [Sendgrid](https://docs.sendgrid.com/api-reference/mail-send/mail-send)
6
+ - [Resend](https://resend.com/docs/api-reference/emails/send-email)
7
+ - [Mailchimp](https://mailchimp.com/developer/transactional/api/messages/send-new-message/)
8
+ - [Mailgun](https://documentation.mailgun.com/en/latest/api-sending.html#examples)
9
+
10
+ > TODO: confirm tests
11
+
12
+ ## Howto
13
+
14
+ ### Sendgrid
15
+
16
+ ```js
17
+ import { MailerSendGrid } from '@storecraft/mailer-providers-http/sendgrid';
18
+
19
+ const mailer = new MailerSendGrid(
20
+ {
21
+ apikey: process.env.SEND_GRID_SECRET
22
+ }
23
+ );
24
+
25
+
26
+ let { success, native_response } = await mailer.email({
27
+ from: {name: 'bob đź‘»', address: process.env.FROM_EMAIL }, // sender address
28
+ to: [ { address: process.env.TO_EMAIL } ], // list of receivers
29
+ subject: 'subject test', // Subject line
30
+ text: 'plain text test', // plain text body
31
+ html: '<p>html test</p>', // html body
32
+ });
33
+
34
+ ```
35
+
36
+ ### Resend
37
+ ```js
38
+ import { MailerResend } from '@storecraft/mailer-providers-http/resend';
39
+
40
+ const mailer = new MailerResend(
41
+ {
42
+ apikey: process.env.RESEND_API_KEY
43
+ }
44
+ );
45
+
46
+ let { success, native_response } = await mailer.email({
47
+ from: {name: 'bob đź‘»', address: process.env.FROM_EMAIL }, // sender address
48
+ to: [ { address: process.env.TO_EMAIL } ], // list of receivers
49
+ subject: 'subject test', // Subject line
50
+ text: 'plain text test', // plain text body
51
+ html: '<p>html test</p>', // html body
52
+ });
53
+
54
+ ```
55
+
56
+
57
+ ### Mailchimp
58
+
59
+ ```js
60
+ import { MailerMailChimp } from '@storecraft/mailer-providers-http/mailchimp';
61
+
62
+ const mailer = new MailerMailChimp(
63
+ {
64
+ apikey: process.env.MAILCHIMP_API_KEY
65
+ }
66
+ );
67
+
68
+ let { success, native_response } = await mailer.email({
69
+ from: {name: 'bob đź‘»', address: process.env.FROM_EMAIL }, // sender address
70
+ to: [ { address: process.env.TO_EMAIL } ], // list of receivers
71
+ subject: 'subject test', // Subject line
72
+ text: 'plain text test', // plain text body
73
+ html: '<p>html test</p>', // html body
74
+ });
75
+
76
+ ```
77
+
78
+
79
+ ### Mailgun
80
+
81
+ ```js
82
+ import { MailerMailgun } from '@storecraft/mailer-providers-http/mailgun';
83
+
84
+ const mailer = new MailerMailgun(
85
+ {
86
+ apikey: process.env.MAILGUN_API_KEY
87
+ }
88
+ );
89
+
90
+ let { success, native_response } = await mailer.email(
91
+ {
92
+ from: {name: 'bob đź‘»', address: process.env.FROM_EMAIL }, // sender address
93
+ to: [ { address: process.env.TO_EMAIL } ], // list of receivers
94
+ subject: 'subject test', // Subject line
95
+ text: 'plain text test', // plain text body
96
+ html: '<p>html test</p>', // html body
97
+ }
98
+ );
99
+
100
+ ```
101
+
102
+
103
+ ```text
104
+ Author: Tomer Shalev (tomer.shalev@gmail.com)
105
+ ```
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * as mailchimp from './mailchimp/index.js'
2
+ export * as mailgun from './mailgun/index.js'
3
+ export * as resend from './resend/index.js'
4
+ export * as sendgrid from './sendgrid/index.js'
@@ -0,0 +1,27 @@
1
+ # `mailchimp` mailer over http
2
+ [mailchimp.com](https://mailchimp.com/developer/transactional/api/messages/send-new-message/) client, that can work on eveywhere with `javascript`. We only rely on `fetch api`.
3
+
4
+ ## Howto
5
+
6
+ ```js
7
+ import { MailerMailChimp } from '@storecraft/mailer-mailchimp-http';
8
+
9
+ const mailer = new MailerMailChimp(
10
+ {
11
+ apikey: process.env.MAILCHIMP_API_KEY
12
+ }
13
+ );
14
+
15
+ let { success, native_response } = await mailer.email({
16
+ from: {name: 'bob đź‘»', address: process.env.FROM_EMAIL }, // sender address
17
+ to: [ { address: process.env.TO_EMAIL } ], // list of receivers
18
+ subject: 'subject test', // Subject line
19
+ text: 'plain text test', // plain text body
20
+ html: '<p>html test</p>', // html body
21
+ });
22
+
23
+ ```
24
+
25
+ ```text
26
+ Author: Tomer Shalev (tomer.shalev@gmail.com)
27
+ ```
@@ -0,0 +1,78 @@
1
+ import { convert_to_base64 } from "./adapter.utils.js";
2
+
3
+ /**
4
+ * @typedef {import("./types.public.js").Config} Config
5
+ * @typedef {import('@storecraft/core/v-mailer').mailer<Config>} mailer
6
+ * @implements {mailer}
7
+ *
8
+ * mailer with mail-chimp / mandrill http api
9
+ */
10
+ export class MailerMailChimp {
11
+
12
+ /** @type {Config} */ #_config;
13
+
14
+ /**
15
+ *
16
+ * @param {Config} config
17
+ */
18
+ constructor(config) {
19
+ this.#_config = config;
20
+ }
21
+
22
+ get config() { return this.#_config; }
23
+
24
+ /**
25
+ *
26
+ * @type {mailer["email"]}
27
+ */
28
+ async email(o) {
29
+
30
+ /** @type {import("./types.private.js").Mailchimp_sendmail} */
31
+ const body = {
32
+ key: this.config.apikey,
33
+ message: {
34
+ from_email: o.from.address,
35
+ from_name: o.from.name,
36
+ to: o.to.map(t => ({ email:t.address, name: t.name, type: 'to'})),
37
+ subject: o.subject,
38
+ html: o.html,
39
+ text: o.text,
40
+ attachments: o.attachments && await Promise.all(
41
+ o.attachments.map(
42
+ /**
43
+ * @returns {Promise<import("./types.private.js").Mailchimp_sendmail_message_attachment>}
44
+ */
45
+ async a => ({
46
+ type: a.content_type,
47
+ name: a.filename,
48
+ content: await convert_to_base64(a.content)
49
+ })
50
+ )
51
+ ),
52
+ }
53
+ }
54
+
55
+ let r;
56
+ try {
57
+ r = await fetch(
58
+ 'https://mandrillapp.com/api/1.0/messages/send',
59
+ {
60
+ body: JSON.stringify(body),
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ },
64
+ method: 'POST',
65
+ }
66
+ );
67
+ } catch (e) {
68
+ console.log(e);
69
+ }
70
+
71
+ return {
72
+ success: r.ok,
73
+ native_response: await r.json()
74
+ }
75
+ }
76
+
77
+ }
78
+
@@ -0,0 +1,26 @@
1
+ import { base64 } from '@storecraft/core/v-crypto'
2
+
3
+ /**
4
+ * @param {import('@storecraft/core/v-mailer').MailObject["attachments"][0]["content"]} c
5
+ */
6
+ export const convert_to_base64 = async (c) => {
7
+ if(c instanceof ArrayBuffer)
8
+ return base64.fromUint8Array(new Uint8Array(c));
9
+ else if(c instanceof ReadableStream) {
10
+ const reader = c.getReader();
11
+ const buffer = []
12
+ while (true) {
13
+ // The `read()` method returns a promise that
14
+ // resolves when a value has been received.
15
+ const { done, value } = await reader.read();
16
+ // Result objects contain two properties:
17
+ // `done` - `true` if the stream has already given you all its data.
18
+ // `value` - Some data. Always `undefined` when `done` is `true`.
19
+ if (done) {
20
+ return base64.fromUint8Array(new Uint8Array(buffer));
21
+ }
22
+ buffer.push(value);
23
+ }
24
+ }
25
+ else return base64.toBase64(c.toString());
26
+ }
@@ -0,0 +1 @@
1
+ export * from './adapter.js'
@@ -0,0 +1,25 @@
1
+ import 'dotenv/config';
2
+ import { test } from 'uvu';
3
+ import * as assert from 'uvu/assert';
4
+ import { MailerMailChimp } from '../index.js';
5
+
6
+ const mailer = new MailerMailChimp(
7
+ {
8
+ apikey: process.env.MAILCHIMP_API_KEY
9
+ }
10
+ );
11
+
12
+ test('send email', async () => {
13
+
14
+ let { success, native_response } = await mailer.email({
15
+ from: {name: 'bob đź‘»', address: process.env.FROM_EMAIL }, // sender address
16
+ to: [ { address: process.env.TO_EMAIL } ], // list of receivers
17
+ subject: 'subject test', // Subject line
18
+ text: 'plain text test', // plain text body
19
+ html: '<p>html test</p>', // html body
20
+ });
21
+
22
+ assert.ok(success, `failed with native_response ${JSON.stringify(native_response, null, 2)}`)
23
+ });
24
+
25
+ test.run();
@@ -0,0 +1,14 @@
1
+ {
2
+ "compileOnSave": false,
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "allowJs": true,
6
+ "checkJs": true,
7
+ "target": "ESNext",
8
+ "resolveJsonModule": true,
9
+ "moduleResolution": "NodeNext",
10
+ "module": "NodeNext",
11
+ "composite": true,
12
+ },
13
+ "include": ["*", "*/*", "src/*"]
14
+ }
@@ -0,0 +1,50 @@
1
+
2
+ export type Mailchimp_sendmail_message_to = {
3
+ /** the email address of the recipient */
4
+ email: string;
5
+ /** the optional display name to use for the recipient */
6
+ name?: string;
7
+ /** the header type to use for the recipient, defaults to "to" if not provided Possible values: "to", "cc", or "bcc". */
8
+ type?: 'to' | 'cc' | 'bcc';
9
+ }
10
+
11
+ export type Mailchimp_sendmail_message_attachment = {
12
+ /** the MIME type of the attachment */
13
+ type?: string;
14
+ /** the file name of the attachment */
15
+ name: string;
16
+ /** the content of the attachment as a base64-encoded string */
17
+ content: string;
18
+ }
19
+
20
+ export type Mailchimp_sendmail_message = {
21
+ /** the full HTML content to be sent */
22
+ html?: string;
23
+ /** optional full text content to be sent */
24
+ text?: string;
25
+ /** the message subject */
26
+ subject: string;
27
+ /** the sender email address */
28
+ from_email: string;
29
+ /** optional from name to be used */
30
+ from_name?: string;
31
+ /** an array of recipient information. */
32
+ to: Mailchimp_sendmail_message_to[];
33
+ /** optional extra headers to add to the message (most headers are allowed) */
34
+ headers?: Record<string, string>;
35
+ /** an array of supported attachments to add to the message */
36
+ attachments?: Mailchimp_sendmail_message_attachment[];
37
+ }
38
+
39
+ export type Mailchimp_sendmail = {
40
+ /** a valid api key */
41
+ key: string;
42
+ /** enable a background sending mode that is optimized for bulk sending. In async mode, messages/send will immediately return a status of "queued" for every recipient. To handle rejections when sending in async mode, set up a webhook for the 'reject' event. Defaults to false for messages with no more than 10 recipients; messages with more than 10 recipients are always sent asynchronously, regardless of the value of async. */
43
+ async?: boolean;
44
+ /** the name of the dedicated ip pool that should be used to send the message. If you do not have any dedicated IPs, this parameter has no effect. If you specify a pool that does not exist, your default pool will be used instead. */
45
+ ip_pool?: string;
46
+ /** when this message should be sent as a UTC timestamp in YYYY-MM-DD HH:MM:SS format. If you specify a time in the past, the message will be sent immediately; for future dates, you're limited to one year from the date of scheduling. */
47
+ send_at?: string;
48
+ /** the information on the message to send */
49
+ message: Mailchimp_sendmail_message;
50
+ }
@@ -0,0 +1,10 @@
1
+ export * from './index.js';
2
+
3
+ /**
4
+ * config
5
+ */
6
+ export type Config = {
7
+ apikey: string,
8
+
9
+ };
10
+
@@ -0,0 +1,29 @@
1
+ # `mailgun` mailer over http
2
+ [mailgun.com](https://documentation.mailgun.com/en/latest/api-sending.html#examples) client, that can work on eveywhere with `javascript`. We only rely on `fetch api`.
3
+
4
+ ## Howto
5
+
6
+ ```js
7
+ import { MailerMailChimp } from '@storecraft/mailer-mailgun-http';
8
+
9
+ const mailer = new MailerMailgun(
10
+ {
11
+ apikey: process.env.MAILGUN_API_KEY
12
+ }
13
+ );
14
+
15
+ let { success, native_response } = await mailer.email(
16
+ {
17
+ from: {name: 'bob đź‘»', address: process.env.FROM_EMAIL }, // sender address
18
+ to: [ { address: process.env.TO_EMAIL } ], // list of receivers
19
+ subject: 'subject test', // Subject line
20
+ text: 'plain text test', // plain text body
21
+ html: '<p>html test</p>', // html body
22
+ }
23
+ );
24
+
25
+ ```
26
+
27
+ ```text
28
+ Author: Tomer Shalev (tomer.shalev@gmail.com)
29
+ ```
@@ -0,0 +1,81 @@
1
+ import { base64 } from "@storecraft/core/v-crypto";
2
+ import { address_to_friendly_name, convert_attachment_to_blob } from "./adapter.utils.js";
3
+
4
+ /**
5
+ * @typedef {import("./types.public.js").Config} Config
6
+ * @typedef {import('@storecraft/core/v-mailer').mailer<Config>} mailer
7
+ * @implements {mailer}
8
+ *
9
+ * mailer with mailgun http api
10
+ */
11
+ export class MailerMailgun {
12
+
13
+ /** @type {Config} */ #_config;
14
+
15
+ /**
16
+ *
17
+ * @param {Config} config
18
+ */
19
+ constructor(config) {
20
+ this.#_config = config;
21
+ }
22
+
23
+ get config() { return this.#_config; }
24
+
25
+ /**
26
+ *
27
+ * @type {mailer["email"]}
28
+ */
29
+ async email(o) {
30
+
31
+ const form = new FormData();
32
+ form.append('from', address_to_friendly_name(o.from));
33
+
34
+ o.to.forEach(
35
+ t => {
36
+ form.append('to', address_to_friendly_name(t));
37
+ }
38
+ );
39
+ form.append('subject', o.subject);
40
+ form.append('text', o.text);
41
+ form.append('html', o.html);
42
+
43
+ if(Array.isArray(o.attachments)) {
44
+ o.attachments.forEach(
45
+ async a => {
46
+ form.append(
47
+ 'attachment',
48
+ await convert_attachment_to_blob(a.content),
49
+ a.filename ?? ''
50
+ );
51
+ }
52
+ )
53
+ }
54
+
55
+ const auth = base64.encode(`api:${this.config.apikey}`, false);
56
+
57
+ let r;
58
+ try {
59
+ r = await fetch(
60
+ `https://api.mailgun.net/v3/${this.config.domain_name}/messages`,
61
+ {
62
+ body: form,
63
+ headers: {
64
+ 'Content-Type': 'multipart/form-data',
65
+ 'Authorization': `Basic ${auth}`
66
+ },
67
+ method: 'POST',
68
+ }
69
+ );
70
+ } catch (e) {
71
+ console.log(e);
72
+ }
73
+
74
+ return {
75
+ success: r.ok,
76
+ native_response: await r.text()
77
+ }
78
+ }
79
+
80
+ }
81
+
@@ -0,0 +1,34 @@
1
+
2
+ /**
3
+ *
4
+ * @param {import('@storecraft/core/v-mailer').MailObject["attachments"][0]["content"]} c
5
+ */
6
+ export const convert_attachment_to_blob = async c => {
7
+ if(c instanceof ArrayBuffer)
8
+ return new Blob([new Uint8Array(c)]);
9
+ else if(c instanceof ReadableStream) {
10
+ const reader = c.getReader();
11
+ const buffer = []
12
+ while (true) {
13
+ // The `read()` method returns a promise that
14
+ // resolves when a value has been received.
15
+ const { done, value } = await reader.read();
16
+ // Result objects contain two properties:
17
+ // `done` - `true` if the stream has already given you all its data.
18
+ // `value` - Some data. Always `undefined` when `done` is `true`.
19
+ if (done) {
20
+ return new Blob([new Uint8Array(buffer)]);
21
+ }
22
+ buffer.push(value);
23
+ }
24
+ }
25
+ else return new Blob([c.toString()]);
26
+ }
27
+
28
+ /**
29
+ *
30
+ * @param {import('@storecraft/core/v-mailer').MailAddress} a
31
+ */
32
+ export const address_to_friendly_name = a => {
33
+ return a.name ? `${a.name} <${a.address}>` : a.address;
34
+ }
@@ -0,0 +1 @@
1
+ export * from './adapter.js'
@@ -0,0 +1,30 @@
1
+ import 'dotenv/config';
2
+ import { test } from 'uvu';
3
+ import * as assert from 'uvu/assert';
4
+ import { MailerMailgun } from '../index.js';
5
+
6
+ const mailer = new MailerMailgun(
7
+ {
8
+ apikey: process.env.MAILGUN_API_KEY,
9
+ domain_name: process.env.YOUR_DOMAIN_NAME
10
+ }
11
+ );
12
+
13
+ test('send email', async () => {
14
+
15
+ let { success, native_response } = await mailer.email({
16
+ from: {name: 'bob đź‘»', address: process.env.FROM_EMAIL }, // sender address
17
+ to: [ { address: process.env.TO_EMAIL } ], // list of receivers
18
+ subject: 'subject test', // Subject line
19
+ text: 'plain text test', // plain text body
20
+ html: '<p>html test</p>', // html body
21
+ });
22
+
23
+ assert.ok(
24
+ success,
25
+ `failed with native_response ${JSON.stringify(native_response, null, 2)}`
26
+ );
27
+
28
+ });
29
+
30
+ test.run();
@@ -0,0 +1,14 @@
1
+ {
2
+ "compileOnSave": false,
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "allowJs": true,
6
+ "checkJs": true,
7
+ "target": "ESNext",
8
+ "resolveJsonModule": true,
9
+ "moduleResolution": "NodeNext",
10
+ "module": "NodeNext",
11
+ "composite": true,
12
+ },
13
+ "include": ["*", "*/*", "src/*"]
14
+ }
File without changes
@@ -0,0 +1,12 @@
1
+ export * from './index.js';
2
+
3
+ /**
4
+ * config
5
+ */
6
+ export type Config = {
7
+ /** your mailgun api key */
8
+ apikey: string,
9
+ /** your registered domain name at mailgun */
10
+ domain_name: string;
11
+ };
12
+
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@storecraft/mailer-providers-http",
3
+ "version": "1.0.0",
4
+ "description": "Official Serverless Friendly e-mail adapters for storecraft",
5
+ "license": "MIT",
6
+ "author": "Tomer Shalev (https://github.com/store-craft)",
7
+ "homepage": "https://github.com/store-craft/storecraft",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/store-craft/storecraft.git",
11
+ "directory": "packages/mailer-providers-http"
12
+ },
13
+ "keywords": [
14
+ "commerce",
15
+ "dashboard",
16
+ "code",
17
+ "storecraft"
18
+ ],
19
+ "type": "module",
20
+ "main": "index.js",
21
+ "types": "./types.public.d.ts",
22
+ "scripts": {
23
+ "mailer-providers-http:test": "uvu -c",
24
+ "mailer-providers-http:publish": "npm publish --access public"
25
+ },
26
+ "exports": {
27
+ ".": {
28
+ "import": "./index.js"
29
+ },
30
+ "./package.json": "./package.json",
31
+ "./mailchimp": {
32
+ "import": "./mailchimp/index.js",
33
+ "types": "./mailchimp/types.public.d.ts"
34
+ },
35
+ "./mailgun": {
36
+ "import": "./mailgun/index.js",
37
+ "types": "./mailgun/types.public.d.ts"
38
+ },
39
+ "./resend": {
40
+ "import": "./resend/index.js",
41
+ "types": "./resend/types.public.d.ts"
42
+ },
43
+ "./sendgrid": {
44
+ "import": "./sendgrid/index.js",
45
+ "types": "./sendgrid/types.public.d.ts"
46
+ }
47
+ },
48
+ "dependencies": {
49
+ "@storecraft/core": "^1.0.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^20.11.0",
53
+ "uvu": "^0.5.6",
54
+ "dotenv": "^16.3.1"
55
+ }
56
+ }
@@ -0,0 +1,27 @@
1
+ # Resend mailer over http
2
+ [Resend.com](https://resend.com/docs/api-reference/emails/send-email) client, that can work on eveywhere with `javascript`. We only rely on `fetch api`
3
+
4
+ ## Howto
5
+
6
+ ```js
7
+ import { MailerResend } from '@storecraft/mailer-resend-http';
8
+
9
+ const mailer = new MailerResend(
10
+ {
11
+ apikey: process.env.RESEND_API_KEY
12
+ }
13
+ );
14
+
15
+ let { success, native_response } = await mailer.email({
16
+ from: {name: 'bob đź‘»', address: process.env.FROM_EMAIL }, // sender address
17
+ to: [ { address: process.env.TO_EMAIL } ], // list of receivers
18
+ subject: 'subject test', // Subject line
19
+ text: 'plain text test', // plain text body
20
+ html: '<p>html test</p>', // html body
21
+ });
22
+
23
+ ```
24
+
25
+ ```text
26
+ Author: Tomer Shalev (tomer.shalev@gmail.com)
27
+ ```
@@ -0,0 +1,76 @@
1
+ import { address_to_friendly_name, convert_to_base64 } from "./adapter.utils.js";
2
+
3
+ /**
4
+ * @typedef {import("./types.public.js").Config} Config
5
+ * @typedef {import('@storecraft/core/v-mailer').mailer<Config>} mailer
6
+ * @implements {mailer}
7
+ *
8
+ * mailer with Resend rest api
9
+ */
10
+ export class MailerResend {
11
+
12
+ /** @type {Config} */ #_config;
13
+
14
+ /**
15
+ *
16
+ * @param {Config} config
17
+ */
18
+ constructor(config) {
19
+ this.#_config = config;
20
+ }
21
+
22
+ get config() { return this.#_config; }
23
+
24
+ /**
25
+ *
26
+ * @type {mailer["email"]}
27
+ */
28
+ async email(o) {
29
+
30
+ /** @type {import("./types.private.js").Resend_sendmail} */
31
+ const body = {
32
+ from: address_to_friendly_name(o.from),
33
+ to: o.to.map(t => t.address),
34
+ subject: o.subject,
35
+ html: o.html,
36
+ text: o.text,
37
+ attachments: o.attachments && await Promise.all(
38
+ o.attachments.map(
39
+ /**
40
+ * @returns {Promise<import("./types.private.js").Resend_sendmail_attachment>}
41
+ */
42
+ async a => (
43
+ {
44
+ filename: a.filename,
45
+ content: await convert_to_base64(a.content)
46
+ }
47
+ )
48
+ )
49
+ ),
50
+ }
51
+
52
+ let r;
53
+ try {
54
+ r = await fetch(
55
+ 'https://api.resend.com/emails',
56
+ {
57
+ body: JSON.stringify(body),
58
+ headers: {
59
+ 'Authorization': `Bearer ${this.config.apikey}`,
60
+ 'Content-Type': 'application/json',
61
+ },
62
+ method: 'POST',
63
+ }
64
+ );
65
+ } catch (e) {
66
+ console.log(e);
67
+ }
68
+
69
+ return {
70
+ success: r.ok,
71
+ native_response: await r.json()
72
+ }
73
+ }
74
+
75
+ }
76
+
@@ -0,0 +1,35 @@
1
+ import { base64 } from '@storecraft/core/v-crypto'
2
+
3
+ /**
4
+ *
5
+ * @param {import('@storecraft/core/v-mailer').MailObject["attachments"][0]["content"]} c
6
+ */
7
+ export const convert_to_base64 = async (c) => {
8
+ if(c instanceof ArrayBuffer)
9
+ return base64.fromUint8Array(new Uint8Array(c));
10
+ else if(c instanceof ReadableStream) {
11
+ const reader = c.getReader();
12
+ const buffer = []
13
+ while (true) {
14
+ // The `read()` method returns a promise that
15
+ // resolves when a value has been received.
16
+ const { done, value } = await reader.read();
17
+ // Result objects contain two properties:
18
+ // `done` - `true` if the stream has already given you all its data.
19
+ // `value` - Some data. Always `undefined` when `done` is `true`.
20
+ if (done) {
21
+ return base64.fromUint8Array(new Uint8Array(buffer));
22
+ }
23
+ buffer.push(value);
24
+ }
25
+ }
26
+ else return base64.toBase64(c.toString());
27
+ }
28
+
29
+ /**
30
+ *
31
+ * @param {import('@storecraft/core/v-mailer').MailAddress} a
32
+ */
33
+ export const address_to_friendly_name = a => {
34
+ return a.name ? `${a.name} <${a.address}>` : a.address;
35
+ }
@@ -0,0 +1 @@
1
+ export * from './adapter.js'
@@ -0,0 +1,25 @@
1
+ import 'dotenv/config';
2
+ import { test } from 'uvu';
3
+ import * as assert from 'uvu/assert';
4
+ import { MailerResend } from '../index.js';
5
+
6
+ const mailer = new MailerResend(
7
+ {
8
+ apikey: process.env.RESEND_API_KEY
9
+ }
10
+ );
11
+
12
+ test('send email', async () => {
13
+
14
+ let { success, native_response } = await mailer.email({
15
+ from: {name: 'bob đź‘»', address: process.env.FROM_EMAIL }, // sender address
16
+ to: [ { address: process.env.TO_EMAIL } ], // list of receivers
17
+ subject: 'subject test', // Subject line
18
+ text: 'plain text test', // plain text body
19
+ html: '<p>html test</p>', // html body
20
+ });
21
+
22
+ assert.ok(success, `failed with native_response ${JSON.stringify(native_response, null, 2)}`)
23
+ });
24
+
25
+ test.run();
@@ -0,0 +1,14 @@
1
+ {
2
+ "compileOnSave": false,
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "allowJs": true,
6
+ "checkJs": true,
7
+ "target": "ESNext",
8
+ "resolveJsonModule": true,
9
+ "moduleResolution": "NodeNext",
10
+ "module": "NodeNext",
11
+ "composite": true,
12
+ },
13
+ "include": ["*", "*/*", "src/*"]
14
+ }
@@ -0,0 +1,41 @@
1
+
2
+ export type Resend_sendmail_tag = {
3
+ /** The name of the email tag. It can only contain ASCII letters (a–z, A–Z), numbers (0–9), underscores (_), or dashes (-). It can contain no more than 256 characters. */
4
+ name: string;
5
+ /** The value of the email tag. It can only contain ASCII letters (a–z, A–Z), numbers (0–9), underscores (_), or dashes (-). It can contain no more than 256 characters. */
6
+ value?: string;
7
+ }
8
+
9
+ export type Resend_sendmail_attachment = {
10
+ /** Content of an attached file, base 64 encoded. */
11
+ content?: string;
12
+ /** Name of attached file. */
13
+ filename?: string;
14
+ /** Path where the attachment file is hosted */
15
+ path?: string;
16
+ }
17
+
18
+ export type Resend_sendmail = {
19
+ /** Sender email address. To include a friendly name, use the format "Your Name <sender@domain.com>". */
20
+ from: string,
21
+ /** Recipient email address. For multiple addresses, send as an array of strings. Max 50. */
22
+ to: string | string[]
23
+ /** Email subject. */
24
+ subject: string,
25
+ /** Bcc recipient email address. For multiple addresses, send as an array of strings. */
26
+ bcc?: string | string[],
27
+ /** Cc recipient email address. For multiple addresses, send as an array of strings. */
28
+ cc?: string | string[],
29
+ /** Reply-to email address. For multiple addresses, send as an array of strings. */
30
+ reply_to?: string | string[],
31
+ /** The HTML version of the message. */
32
+ html?: string;
33
+ /** The plain text version of the message. */
34
+ text?: string;
35
+ /** Custom headers to add to the email. */
36
+ headers?: Record<string, string>,
37
+ /** Email tags */
38
+ tags?: Resend_sendmail_tag[],
39
+ /** Filename and content of attachments (max 40mb per email) */
40
+ attachments?: Resend_sendmail_attachment[];
41
+ }
@@ -0,0 +1,9 @@
1
+ export * from './index.js';
2
+
3
+ /**
4
+ * config
5
+ */
6
+ export type Config = {
7
+ apikey: string,
8
+ };
9
+
@@ -0,0 +1,27 @@
1
+ # SendGrid mailer over http
2
+ [sendgrid.com](https://docs.sendgrid.com/api-reference/mail-send/mail-send) client, that can work on eveywhere with `javascript`. We only rely on `fetch api`.
3
+
4
+ ## Howto
5
+
6
+ ```js
7
+ import { MailerSendGrid } from '@storecraft/mailer-sendgrid-http';
8
+
9
+ const mailer = new MailerSendGrid(
10
+ {
11
+ apikey: process.env.SEND_GRID_SECRET
12
+ }
13
+ );
14
+
15
+ let { success, native_response } = await mailer.email({
16
+ from: {name: 'bob đź‘»', address: process.env.FROM_EMAIL }, // sender address
17
+ to: [ { address: process.env.TO_EMAIL } ], // list of receivers
18
+ subject: 'subject test', // Subject line
19
+ text: 'plain text test', // plain text body
20
+ html: '<p>html test</p>', // html body
21
+ });
22
+
23
+ ```
24
+
25
+ ```text
26
+ Author: Tomer Shalev (tomer.shalev@gmail.com)
27
+ ```
@@ -0,0 +1,91 @@
1
+ import { convert_to_base64 } from "./adapter.utils.js";
2
+
3
+ /**
4
+ * @typedef {import("./types.public.js").Config} Config
5
+ * @typedef {import('@storecraft/core/v-mailer').mailer<Config>} mailer
6
+ * @implements {mailer}
7
+ *
8
+ * mailer with sendgrid http api
9
+ */
10
+ export class MailerSendGrid {
11
+
12
+ /** @type {Config} */ #_config;
13
+
14
+ /**
15
+ *
16
+ * @param {Config} config
17
+ */
18
+ constructor(config) {
19
+ this.#_config = config;
20
+ }
21
+
22
+ get config() { return this.#_config; }
23
+
24
+ /**
25
+ *
26
+ * @type {mailer["email"]}
27
+ */
28
+ async email(o) {
29
+
30
+ /** @type {import("./types.private.js").SendgridV3_sendmail} */
31
+ const body = {
32
+ content: [
33
+ {
34
+ type: 'text/html', value: o.html
35
+ },
36
+ {
37
+ type: 'text/plain', value: o.text
38
+ }
39
+ ].filter(c => Boolean(c.value)),
40
+ from: { email: o.from.address, name: o.from.name ?? ''},
41
+ subject: o.subject,
42
+ attachments: o.attachments && await Promise.all(
43
+ o.attachments.map(
44
+ async a => (
45
+ {
46
+ content_id: a.content_id,
47
+ disposition: a.disposition,
48
+ filename: a.filename,
49
+ type: a.content_type,
50
+ content: await convert_to_base64(a.content)
51
+ }
52
+ )
53
+ )
54
+ ),
55
+ personalizations: o.to.map(
56
+ t => (
57
+ {
58
+ to: {
59
+ email: t.address,
60
+ name: t.name ?? ''
61
+ }
62
+ }
63
+ )
64
+ )
65
+ }
66
+
67
+ let r;
68
+ try {
69
+ r = await fetch(
70
+ 'https://api.sendgrid.com/v3/mail/send',
71
+ {
72
+ body: JSON.stringify(body),
73
+ headers: {
74
+ 'Authorization': `Bearer ${this.config.apikey}`,
75
+ 'Content-Type': 'application/json',
76
+ },
77
+ method: 'POST',
78
+ }
79
+ );
80
+ } catch (e) {
81
+ console.log(e);
82
+ }
83
+
84
+ return {
85
+ success: r.ok,
86
+ native_response: await r.json()
87
+ }
88
+ }
89
+
90
+ }
91
+
@@ -0,0 +1,27 @@
1
+ import { base64 } from '@storecraft/core/v-crypto'
2
+
3
+ /**
4
+ *
5
+ * @param {import('@storecraft/core/v-mailer').MailObject["attachments"][0]["content"]} c
6
+ */
7
+ export const convert_to_base64 = async (c) => {
8
+ if(c instanceof ArrayBuffer)
9
+ return base64.fromUint8Array(new Uint8Array(c));
10
+ else if(c instanceof ReadableStream) {
11
+ const reader = c.getReader();
12
+ const buffer = []
13
+ while (true) {
14
+ // The `read()` method returns a promise that
15
+ // resolves when a value has been received.
16
+ const { done, value } = await reader.read();
17
+ // Result objects contain two properties:
18
+ // `done` - `true` if the stream has already given you all its data.
19
+ // `value` - Some data. Always `undefined` when `done` is `true`.
20
+ if (done) {
21
+ return base64.fromUint8Array(new Uint8Array(buffer));
22
+ }
23
+ buffer.push(value);
24
+ }
25
+ }
26
+ else return base64.toBase64(c.toString());
27
+ }
@@ -0,0 +1 @@
1
+ export * from './adapter.js'
@@ -0,0 +1,25 @@
1
+ import 'dotenv/config';
2
+ import { test } from 'uvu';
3
+ import * as assert from 'uvu/assert';
4
+ import { MailerSendGrid } from '../index.js';
5
+
6
+ const mailer = new MailerSendGrid(
7
+ {
8
+ apikey: process.env.SEND_GRID_SECRET
9
+ }
10
+ );
11
+
12
+ test('send email', async () => {
13
+
14
+ let { success, native_response } = await mailer.email({
15
+ from: {name: 'bob đź‘»', address: process.env.FROM_EMAIL }, // sender address
16
+ to: [ { address: process.env.TO_EMAIL } ], // list of receivers
17
+ subject: 'subject test', // Subject line
18
+ text: 'plain text test', // plain text body
19
+ html: '<p>html test</p>', // html body
20
+ });
21
+
22
+ assert.ok(success, `failed with native_response ${JSON.stringify(native_response, null, 2)}`)
23
+ });
24
+
25
+ test.run();
@@ -0,0 +1,14 @@
1
+ {
2
+ "compileOnSave": false,
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "allowJs": true,
6
+ "checkJs": true,
7
+ "target": "ESNext",
8
+ "resolveJsonModule": true,
9
+ "moduleResolution": "NodeNext",
10
+ "module": "NodeNext",
11
+ "composite": true,
12
+ },
13
+ "include": ["*", "*/*", "src/*"]
14
+ }
@@ -0,0 +1,58 @@
1
+
2
+ export type SendgridV3_sendmail_content = {
3
+ /** The MIME type of the content you are including in your email (e.g., “text/plain” or “text/html”). */
4
+ type: string,
5
+ /** The actual content of the specified MIME type that you are including in your email. */
6
+ value: string,
7
+ }
8
+
9
+ export type SendgridV3_sendmail_attachment = {
10
+ /** The Base64 encoded content of the attachment. */
11
+ content: string;
12
+ /** The MIME type of the content you are attaching (e.g., “text/plain” or “text/html”). */
13
+ type?: string;
14
+ /** The attachment's filename. */
15
+ filename: string;
16
+ /**
17
+ * The attachment's content-disposition, specifying how you would like the attachment
18
+ * to be displayed. For example, “inline” results in the attached file are displayed
19
+ * automatically within the message while “attachment” results in the attached file
20
+ * require some action to be taken before it is displayed, such as opening or downloading the file.
21
+ * default: attachment
22
+ * Allowed Values: inline, attachment
23
+ */
24
+ disposition: 'inline' | 'attachment';
25
+ /** The attachment's content ID. This is used when the disposition is set to “inline” and the attachment is an image, allowing the file to be displayed within the body of your email. */
26
+ content_id: string;
27
+ }
28
+
29
+ export type SendgridV3_sendmail_address = {
30
+ /** The 'From' email address used to deliver the message. This address should be a verified sender in your Twilio SendGrid account. format: email */
31
+ email: string,
32
+ /** A name or title associated with the sending email address. */
33
+ name?: string
34
+ }
35
+
36
+ export type SendgridV3_sendmail_personalization = {
37
+ from?: SendgridV3_sendmail_address,
38
+ to: SendgridV3_sendmail_address,
39
+ /** An array of recipients who will receive a copy of your email. Each object in this array must contain the recipient's email address. Each object in the array may optionally contain the recipient's name. maxItems: 1000 */
40
+ cc?: SendgridV3_sendmail_address[],
41
+ /** An array of recipients who will receive a copy of your email. Each object in this array must contain the recipient's email address. Each object in the array may optionally contain the recipient's name. maxItems: 1000 */
42
+ bcc?: SendgridV3_sendmail_address[],
43
+ /** The subject of your email. See character length requirements according to RFC 2822. */
44
+ subject?: string
45
+
46
+ }
47
+
48
+ export type SendgridV3_sendmail = {
49
+ from: SendgridV3_sendmail_address,
50
+ /** The global or 'message level' subject of your email. This may be overridden by subject lines set in personalizations. minLength: 1 */
51
+ subject: string,
52
+ /** An array where you can specify the content of your email. You can include multiple MIME types of content, but you must specify at least one MIME type. To include more than one MIME type, add another object to the array containing the type and value parameters. */
53
+ content: SendgridV3_sendmail_content[],
54
+ /** An array of objects where you can specify any attachments you want to include. */
55
+ attachments?: SendgridV3_sendmail_attachment[],
56
+ /** An array of messages and their metadata. Each object within personalizations can be thought of as an envelope - it defines who should receive an individual message and how that message should be handled. See our Personalizations documentation for examples. */
57
+ personalizations: SendgridV3_sendmail_personalization[]
58
+ }
@@ -0,0 +1,10 @@
1
+ export * from './index.js';
2
+
3
+ /**
4
+ * config
5
+ */
6
+ export type Config = {
7
+ apikey: string,
8
+
9
+ };
10
+
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compileOnSave": false,
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "allowJs": true,
6
+ "checkJs": true,
7
+ "target": "ESNext",
8
+ "resolveJsonModule": true,
9
+ "moduleResolution": "NodeNext",
10
+ "module": "NodeNext",
11
+ "composite": true,
12
+ },
13
+ "include": ["*", "*/*"]
14
+ }