@react-email/button 0.0.10 → 0.0.11

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.
@@ -0,0 +1,6 @@
1
+ import * as React from 'react';
2
+
3
+ type ButtonProps = React.ComponentPropsWithoutRef<"a">;
4
+ declare const Button: React.FC<Readonly<ButtonProps>>;
5
+
6
+ export { Button };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
- type RootProps = React.ComponentPropsWithoutRef<"a">;
4
- interface ButtonProps extends RootProps {
5
- pX?: number;
6
- pY?: number;
7
- }
8
- declare const Button: React.ForwardRefExoticComponent<Readonly<ButtonProps> & React.RefAttributes<HTMLAnchorElement>>;
3
+ type ButtonProps = React.ComponentPropsWithoutRef<"a">;
4
+ declare const Button: React.FC<Readonly<ButtonProps>>;
9
5
 
10
- export { Button, ButtonProps };
6
+ export { Button };
package/dist/index.js CHANGED
@@ -1,10 +1,37 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
3
+ var __defProps = Object.defineProperties;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
+ var __spreadValues = (a, b) => {
12
+ for (var prop in b || (b = {}))
13
+ if (__hasOwnProp.call(b, prop))
14
+ __defNormalProp(a, prop, b[prop]);
15
+ if (__getOwnPropSymbols)
16
+ for (var prop of __getOwnPropSymbols(b)) {
17
+ if (__propIsEnum.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ }
20
+ return a;
21
+ };
22
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
+ var __objRest = (source, exclude) => {
24
+ var target = {};
25
+ for (var prop in source)
26
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
27
+ target[prop] = source[prop];
28
+ if (source != null && __getOwnPropSymbols)
29
+ for (var prop of __getOwnPropSymbols(source)) {
30
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
31
+ target[prop] = source[prop];
32
+ }
33
+ return target;
34
+ };
8
35
  var __export = (target, all) => {
9
36
  for (var name in all)
10
37
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -17,10 +44,6 @@ var __copyProps = (to, from, except, desc) => {
17
44
  }
18
45
  return to;
19
46
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
- mod
23
- ));
24
47
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
48
 
26
49
  // src/index.ts
@@ -30,69 +53,159 @@ __export(src_exports, {
30
53
  });
31
54
  module.exports = __toCommonJS(src_exports);
32
55
 
33
- // src/button.tsx
34
- var React = __toESM(require("react"));
35
-
36
56
  // src/utils/px-to-pt.ts
37
- var pxToPt = (px) => isNaN(Number(px)) ? null : parseInt(px, 10) * 3 / 4;
57
+ var pxToPt = (px) => typeof px === "number" && !isNaN(Number(px)) ? px * 3 / 4 : null;
58
+
59
+ // src/utils/parse-padding.ts
60
+ function convertToPx(value) {
61
+ let px = 0;
62
+ if (!value) {
63
+ return px;
64
+ }
65
+ if (typeof value === "number") {
66
+ return value;
67
+ }
68
+ const matches = value.match(/^([\d.]+)(px|em|rem|%)$/);
69
+ if (matches && matches.length === 3) {
70
+ const numValue = parseFloat(matches[1]);
71
+ const unit = matches[2];
72
+ switch (unit) {
73
+ case "px":
74
+ return numValue;
75
+ case "em":
76
+ case "rem":
77
+ px = numValue * 16;
78
+ return px;
79
+ case "%":
80
+ px = numValue / 100 * 600;
81
+ return px;
82
+ default:
83
+ return numValue;
84
+ }
85
+ } else {
86
+ return 0;
87
+ }
88
+ }
89
+ function parsePadding({
90
+ padding = "",
91
+ paddingTop,
92
+ paddingRight,
93
+ paddingBottom,
94
+ paddingLeft
95
+ }) {
96
+ let pt = 0;
97
+ let pr = 0;
98
+ let pb = 0;
99
+ let pl = 0;
100
+ if (typeof padding === "number") {
101
+ pt = padding;
102
+ pr = padding;
103
+ pb = padding;
104
+ pl = padding;
105
+ } else {
106
+ const values = padding.split(/\s+/);
107
+ switch (values.length) {
108
+ case 1:
109
+ pt = convertToPx(values[0]);
110
+ pr = convertToPx(values[0]);
111
+ pb = convertToPx(values[0]);
112
+ pl = convertToPx(values[0]);
113
+ break;
114
+ case 2:
115
+ pt = convertToPx(values[0]);
116
+ pb = convertToPx(values[0]);
117
+ pr = convertToPx(values[1]);
118
+ pl = convertToPx(values[1]);
119
+ break;
120
+ case 3:
121
+ pt = convertToPx(values[0]);
122
+ pr = convertToPx(values[1]);
123
+ pl = convertToPx(values[1]);
124
+ pb = convertToPx(values[2]);
125
+ break;
126
+ case 4:
127
+ pt = convertToPx(values[0]);
128
+ pr = convertToPx(values[1]);
129
+ pb = convertToPx(values[2]);
130
+ pl = convertToPx(values[3]);
131
+ break;
132
+ default:
133
+ break;
134
+ }
135
+ }
136
+ return {
137
+ pt: paddingTop ? convertToPx(paddingTop) : pt,
138
+ pr: paddingRight ? convertToPx(paddingRight) : pr,
139
+ pb: paddingBottom ? convertToPx(paddingBottom) : pb,
140
+ pl: paddingLeft ? convertToPx(paddingLeft) : pl
141
+ };
142
+ }
38
143
 
39
144
  // src/button.tsx
40
145
  var import_jsx_runtime = require("react/jsx-runtime");
41
- var Button = React.forwardRef(
42
- ({ children, style, pX = 0, pY = 0, target = "_blank", ...props }, forwardedRef) => {
43
- const y = (pY || 0) * 2;
44
- const textRaise = pxToPt(y.toString());
45
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
46
- "a",
47
- {
48
- ...props,
49
- ref: forwardedRef,
50
- "data-id": "react-email-button",
51
- target,
52
- style: buttonStyle({ ...style, pX, pY }),
53
- children: [
54
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
55
- "span",
56
- {
57
- dangerouslySetInnerHTML: {
58
- __html: `<!--[if mso]><i style="letter-spacing: ${pX}px;mso-font-width:-100%;mso-text-raise:${textRaise}" hidden>&nbsp;</i><![endif]-->`
59
- }
146
+ var Button = (_a) => {
147
+ var _b = _a, {
148
+ children,
149
+ style,
150
+ target = "_blank"
151
+ } = _b, props = __objRest(_b, [
152
+ "children",
153
+ "style",
154
+ "target"
155
+ ]);
156
+ const { pt, pr, pb, pl } = parsePadding({
157
+ padding: style == null ? void 0 : style.padding,
158
+ paddingLeft: style == null ? void 0 : style.paddingLeft,
159
+ paddingRight: style == null ? void 0 : style.paddingRight,
160
+ paddingTop: style == null ? void 0 : style.paddingTop,
161
+ paddingBottom: style == null ? void 0 : style.paddingBottom
162
+ });
163
+ const y = pt + pb;
164
+ const textRaise = pxToPt(y);
165
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
166
+ "a",
167
+ __spreadProps(__spreadValues({}, props), {
168
+ style: buttonStyle(__spreadProps(__spreadValues({}, style), { pt, pr, pb, pl })),
169
+ target,
170
+ children: [
171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
172
+ "span",
173
+ {
174
+ dangerouslySetInnerHTML: {
175
+ __html: `<!--[if mso]><i style="letter-spacing: ${pl}px;mso-font-width:-100%;mso-text-raise:${textRaise}" hidden>&nbsp;</i><![endif]-->`
60
176
  }
61
- ),
62
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: buttonTextStyle(pY), children }),
63
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
64
- "span",
65
- {
66
- dangerouslySetInnerHTML: {
67
- __html: `<!--[if mso]><i style="letter-spacing: ${pX}px;mso-font-width:-100%" hidden>&nbsp;</i><![endif]-->`
68
- }
177
+ }
178
+ ),
179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: buttonTextStyle(pb), children }),
180
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
181
+ "span",
182
+ {
183
+ dangerouslySetInnerHTML: {
184
+ __html: `<!--[if mso]><i style="letter-spacing: ${pr}px;mso-font-width:-100%" hidden>&nbsp;</i><![endif]-->`
69
185
  }
70
- )
71
- ]
72
- }
73
- );
74
- }
75
- );
76
- Button.displayName = "Button";
186
+ }
187
+ )
188
+ ]
189
+ })
190
+ );
191
+ };
77
192
  var buttonStyle = (style) => {
78
- const { pY, pX, ...rest } = style || {};
79
- return {
80
- ...rest,
193
+ const _a = style || {}, { pt, pr, pb, pl } = _a, rest = __objRest(_a, ["pt", "pr", "pb", "pl"]);
194
+ return __spreadProps(__spreadValues({}, rest), {
81
195
  lineHeight: "100%",
82
196
  textDecoration: "none",
83
197
  display: "inline-block",
84
198
  maxWidth: "100%",
85
- padding: `${pY}px ${pX}px`
86
- };
199
+ padding: `${pt}px ${pr}px ${pb}px ${pl}px`
200
+ });
87
201
  };
88
- var buttonTextStyle = (pY) => {
89
- const paddingY = pY || 0;
202
+ var buttonTextStyle = (pb) => {
90
203
  return {
91
204
  maxWidth: "100%",
92
205
  display: "inline-block",
93
206
  lineHeight: "120%",
94
207
  msoPaddingAlt: "0px",
95
- msoTextRaise: pxToPt(paddingY.toString())
208
+ msoTextRaise: pxToPt(pb || 0)
96
209
  };
97
210
  };
98
211
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.mjs CHANGED
@@ -1,66 +1,188 @@
1
- // src/button.tsx
2
- import * as React from "react";
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __objRest = (source, exclude) => {
21
+ var target = {};
22
+ for (var prop in source)
23
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
+ target[prop] = source[prop];
25
+ if (source != null && __getOwnPropSymbols)
26
+ for (var prop of __getOwnPropSymbols(source)) {
27
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
+ target[prop] = source[prop];
29
+ }
30
+ return target;
31
+ };
3
32
 
4
33
  // src/utils/px-to-pt.ts
5
- var pxToPt = (px) => isNaN(Number(px)) ? null : parseInt(px, 10) * 3 / 4;
34
+ var pxToPt = (px) => typeof px === "number" && !isNaN(Number(px)) ? px * 3 / 4 : null;
35
+
36
+ // src/utils/parse-padding.ts
37
+ function convertToPx(value) {
38
+ let px = 0;
39
+ if (!value) {
40
+ return px;
41
+ }
42
+ if (typeof value === "number") {
43
+ return value;
44
+ }
45
+ const matches = value.match(/^([\d.]+)(px|em|rem|%)$/);
46
+ if (matches && matches.length === 3) {
47
+ const numValue = parseFloat(matches[1]);
48
+ const unit = matches[2];
49
+ switch (unit) {
50
+ case "px":
51
+ return numValue;
52
+ case "em":
53
+ case "rem":
54
+ px = numValue * 16;
55
+ return px;
56
+ case "%":
57
+ px = numValue / 100 * 600;
58
+ return px;
59
+ default:
60
+ return numValue;
61
+ }
62
+ } else {
63
+ return 0;
64
+ }
65
+ }
66
+ function parsePadding({
67
+ padding = "",
68
+ paddingTop,
69
+ paddingRight,
70
+ paddingBottom,
71
+ paddingLeft
72
+ }) {
73
+ let pt = 0;
74
+ let pr = 0;
75
+ let pb = 0;
76
+ let pl = 0;
77
+ if (typeof padding === "number") {
78
+ pt = padding;
79
+ pr = padding;
80
+ pb = padding;
81
+ pl = padding;
82
+ } else {
83
+ const values = padding.split(/\s+/);
84
+ switch (values.length) {
85
+ case 1:
86
+ pt = convertToPx(values[0]);
87
+ pr = convertToPx(values[0]);
88
+ pb = convertToPx(values[0]);
89
+ pl = convertToPx(values[0]);
90
+ break;
91
+ case 2:
92
+ pt = convertToPx(values[0]);
93
+ pb = convertToPx(values[0]);
94
+ pr = convertToPx(values[1]);
95
+ pl = convertToPx(values[1]);
96
+ break;
97
+ case 3:
98
+ pt = convertToPx(values[0]);
99
+ pr = convertToPx(values[1]);
100
+ pl = convertToPx(values[1]);
101
+ pb = convertToPx(values[2]);
102
+ break;
103
+ case 4:
104
+ pt = convertToPx(values[0]);
105
+ pr = convertToPx(values[1]);
106
+ pb = convertToPx(values[2]);
107
+ pl = convertToPx(values[3]);
108
+ break;
109
+ default:
110
+ break;
111
+ }
112
+ }
113
+ return {
114
+ pt: paddingTop ? convertToPx(paddingTop) : pt,
115
+ pr: paddingRight ? convertToPx(paddingRight) : pr,
116
+ pb: paddingBottom ? convertToPx(paddingBottom) : pb,
117
+ pl: paddingLeft ? convertToPx(paddingLeft) : pl
118
+ };
119
+ }
6
120
 
7
121
  // src/button.tsx
8
122
  import { jsx, jsxs } from "react/jsx-runtime";
9
- var Button = React.forwardRef(
10
- ({ children, style, pX = 0, pY = 0, target = "_blank", ...props }, forwardedRef) => {
11
- const y = (pY || 0) * 2;
12
- const textRaise = pxToPt(y.toString());
13
- return /* @__PURE__ */ jsxs(
14
- "a",
15
- {
16
- ...props,
17
- ref: forwardedRef,
18
- "data-id": "react-email-button",
19
- target,
20
- style: buttonStyle({ ...style, pX, pY }),
21
- children: [
22
- /* @__PURE__ */ jsx(
23
- "span",
24
- {
25
- dangerouslySetInnerHTML: {
26
- __html: `<!--[if mso]><i style="letter-spacing: ${pX}px;mso-font-width:-100%;mso-text-raise:${textRaise}" hidden>&nbsp;</i><![endif]-->`
27
- }
123
+ var Button = (_a) => {
124
+ var _b = _a, {
125
+ children,
126
+ style,
127
+ target = "_blank"
128
+ } = _b, props = __objRest(_b, [
129
+ "children",
130
+ "style",
131
+ "target"
132
+ ]);
133
+ const { pt, pr, pb, pl } = parsePadding({
134
+ padding: style == null ? void 0 : style.padding,
135
+ paddingLeft: style == null ? void 0 : style.paddingLeft,
136
+ paddingRight: style == null ? void 0 : style.paddingRight,
137
+ paddingTop: style == null ? void 0 : style.paddingTop,
138
+ paddingBottom: style == null ? void 0 : style.paddingBottom
139
+ });
140
+ const y = pt + pb;
141
+ const textRaise = pxToPt(y);
142
+ return /* @__PURE__ */ jsxs(
143
+ "a",
144
+ __spreadProps(__spreadValues({}, props), {
145
+ style: buttonStyle(__spreadProps(__spreadValues({}, style), { pt, pr, pb, pl })),
146
+ target,
147
+ children: [
148
+ /* @__PURE__ */ jsx(
149
+ "span",
150
+ {
151
+ dangerouslySetInnerHTML: {
152
+ __html: `<!--[if mso]><i style="letter-spacing: ${pl}px;mso-font-width:-100%;mso-text-raise:${textRaise}" hidden>&nbsp;</i><![endif]-->`
28
153
  }
29
- ),
30
- /* @__PURE__ */ jsx("span", { style: buttonTextStyle(pY), children }),
31
- /* @__PURE__ */ jsx(
32
- "span",
33
- {
34
- dangerouslySetInnerHTML: {
35
- __html: `<!--[if mso]><i style="letter-spacing: ${pX}px;mso-font-width:-100%" hidden>&nbsp;</i><![endif]-->`
36
- }
154
+ }
155
+ ),
156
+ /* @__PURE__ */ jsx("span", { style: buttonTextStyle(pb), children }),
157
+ /* @__PURE__ */ jsx(
158
+ "span",
159
+ {
160
+ dangerouslySetInnerHTML: {
161
+ __html: `<!--[if mso]><i style="letter-spacing: ${pr}px;mso-font-width:-100%" hidden>&nbsp;</i><![endif]-->`
37
162
  }
38
- )
39
- ]
40
- }
41
- );
42
- }
43
- );
44
- Button.displayName = "Button";
163
+ }
164
+ )
165
+ ]
166
+ })
167
+ );
168
+ };
45
169
  var buttonStyle = (style) => {
46
- const { pY, pX, ...rest } = style || {};
47
- return {
48
- ...rest,
170
+ const _a = style || {}, { pt, pr, pb, pl } = _a, rest = __objRest(_a, ["pt", "pr", "pb", "pl"]);
171
+ return __spreadProps(__spreadValues({}, rest), {
49
172
  lineHeight: "100%",
50
173
  textDecoration: "none",
51
174
  display: "inline-block",
52
175
  maxWidth: "100%",
53
- padding: `${pY}px ${pX}px`
54
- };
176
+ padding: `${pt}px ${pr}px ${pb}px ${pl}px`
177
+ });
55
178
  };
56
- var buttonTextStyle = (pY) => {
57
- const paddingY = pY || 0;
179
+ var buttonTextStyle = (pb) => {
58
180
  return {
59
181
  maxWidth: "100%",
60
182
  display: "inline-block",
61
183
  lineHeight: "120%",
62
184
  msoPaddingAlt: "0px",
63
- msoTextRaise: pxToPt(paddingY.toString())
185
+ msoTextRaise: pxToPt(pb || 0)
64
186
  };
65
187
  };
66
188
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-email/button",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "A link that is styled to look like a button",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -24,13 +24,11 @@
24
24
  "license": "MIT",
25
25
  "scripts": {
26
26
  "build": "tsup src/index.ts --format esm,cjs --dts --external react",
27
- "dev": "tsup src/index.ts --format esm,cjs --dts --external react --watch",
28
- "lint": "eslint",
29
27
  "clean": "rm -rf dist",
30
- "test": "jest",
31
- "test:watch": "jest --watch",
32
- "format:check": "prettier --check \"**/*.{ts,tsx,md}\"",
33
- "format": "prettier --write \"**/*.{ts,tsx,md}\""
28
+ "dev": "tsup src/index.ts --format esm,cjs --dts --external react --watch",
29
+ "lint": "eslint .",
30
+ "test:watch": "vitest",
31
+ "test": "vitest run"
34
32
  },
35
33
  "repository": {
36
34
  "type": "git",
@@ -42,23 +40,16 @@
42
40
  "email"
43
41
  ],
44
42
  "engines": {
45
- "node": ">=16.0.0"
43
+ "node": ">=18.0.0"
46
44
  },
47
- "dependencies": {
45
+ "peerDependencies": {
48
46
  "react": "18.2.0"
49
47
  },
50
48
  "devDependencies": {
51
49
  "@babel/preset-react": "7.22.5",
52
- "@react-email/render": "0.0.6",
53
- "@types/jest": "29.5.3",
54
- "@types/react": "18.0.20",
55
- "@types/react-dom": "18.0.6",
56
- "babel-jest": "28.1.3",
57
- "eslint": "8.45.0",
58
- "jest": "28.1.3",
59
- "prettier": "3.0.0",
60
- "ts-jest": "28.0.8",
61
- "tsup": "6.2.3",
50
+ "@react-email/render": "workspace:*",
51
+ "eslint-config-custom": "workspace:*",
52
+ "tsconfig": "workspace:*",
62
53
  "typescript": "5.1.6"
63
54
  },
64
55
  "publishConfig": {
package/readme.md CHANGED
@@ -56,7 +56,7 @@ This component was tested using the most popular email clients.
56
56
 
57
57
  | <img src="https://react.email/static/icons/gmail.svg" width="48px" height="48px" alt="Gmail logo"> | <img src="https://react.email/static/icons/apple-mail.svg" width="48px" height="48px" alt="Apple Mail"> | <img src="https://react.email/static/icons/outlook.svg" width="48px" height="48px" alt="Outlook logo"> | <img src="https://react.email/static/icons/yahoo-mail.svg" width="48px" height="48px" alt="Yahoo! Mail logo"> | <img src="https://react.email/static/icons/hey.svg" width="48px" height="48px" alt="HEY logo"> | <img src="https://react.email/static/icons/superhuman.svg" width="48px" height="48px" alt="Superhuman logo"> |
58
58
  | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
59
- | Gmail ✔ | Apple Mail ✔ | Outlook ✔ | Yahoo! Mail ✔ | HEY ✔ | Superhuman ✔ |
59
+ | Gmail ✔ | Apple Mail ✔ | Outlook ✔ | Yahoo! Mail ✔ | HEY ✔ | Superhuman ✔ |
60
60
 
61
61
  ## License
62
62