@react-email/tailwind 0.0.7 → 0.0.9

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,10 @@
1
+ import * as React from 'react';
2
+ import { TailwindConfig } from 'tw-to-css';
3
+
4
+ interface TailwindProps {
5
+ children: React.ReactNode;
6
+ config?: TailwindConfig;
7
+ }
8
+ declare const Tailwind: React.FC<TailwindProps>;
9
+
10
+ export { Tailwind, TailwindProps };
package/dist/index.js CHANGED
@@ -1,10 +1,27 @@
1
1
  "use strict";
2
2
  var __create = Object.create;
3
3
  var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
5
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
9
  var __getProtoOf = Object.getPrototypeOf;
7
10
  var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __spreadValues = (a, b) => {
14
+ for (var prop in b || (b = {}))
15
+ if (__hasOwnProp.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ if (__getOwnPropSymbols)
18
+ for (var prop of __getOwnPropSymbols(b)) {
19
+ if (__propIsEnum.call(b, prop))
20
+ __defNormalProp(a, prop, b[prop]);
21
+ }
22
+ return a;
23
+ };
24
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
8
25
  var __export = (target, all) => {
9
26
  for (var name in all)
10
27
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -18,6 +35,10 @@ var __copyProps = (to, from, except, desc) => {
18
35
  return to;
19
36
  };
20
37
  var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
38
+ // If the importer is in node compatibility mode or this is not an ESM
39
+ // file that has been converted to a CommonJS file using a Babel-
40
+ // compatible transform (i.e. "__esModule" has not been set), then set
41
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
42
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
43
  mod
23
44
  ));
@@ -42,18 +63,19 @@ var Tailwind = ({ children, config }) => {
42
63
  });
43
64
  const newChildren = React.Children.toArray(children);
44
65
  const fullHTML = (0, import_server.renderToStaticMarkup)(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: newChildren }));
45
- const headStyle = getMediaQueryCSS(
46
- twi(fullHTML, {
47
- merge: false,
48
- ignoreMediaQueries: false
49
- })
50
- );
51
- const hasResponsiveStyles = /@media[^{]+\{(?<content>[\s\S]+?)\}\s*\}/gm.test(
66
+ const tailwindCss = twi(fullHTML, {
67
+ merge: false,
68
+ ignoreMediaQueries: false
69
+ });
70
+ const css = cleanCss(tailwindCss);
71
+ const cssMap = makeCssMap(css);
72
+ const headStyle = getMediaQueryCss(css);
73
+ const hasResponsiveStyles = new RegExp("@media[^{]+\\{(?<content>[\\s\\S]+?)\\}\\s*\\}", "gm").test(
52
74
  headStyle
53
75
  );
54
76
  const hasHTML = /<html[^>]*>/gm.test(fullHTML);
55
77
  const hasHead = /<head[^>]*>/gm.test(fullHTML);
56
- if (hasResponsiveStyles && !hasHTML && !hasHead) {
78
+ if (hasResponsiveStyles && (!hasHTML || !hasHead)) {
57
79
  throw new Error(
58
80
  "Tailwind: To use responsive styles you must have a <html> and <head> element in your template."
59
81
  );
@@ -69,26 +91,28 @@ var Tailwind = ({ children, config }) => {
69
91
  if (hasResponsiveStyles && hasHead && domNode.name === "head") {
70
92
  let newDomNode = null;
71
93
  if (domNode.children) {
72
- const style = domNode.children.find(
73
- (child2) => child2 instanceof import_html_react_parser.Element && child2.name === "style"
74
- );
75
94
  const props = (0, import_html_react_parser.attributesToProps)(domNode.attribs);
76
- newDomNode = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("head", { ...props, children: style && style instanceof import_html_react_parser.Element ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `${style.children} ${headStyle}` }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: headStyle }) });
95
+ newDomNode = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("head", __spreadProps(__spreadValues({}, props), { children: [
96
+ (0, import_html_react_parser.domToReact)(domNode.children),
97
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: headStyle })
98
+ ] }));
77
99
  }
78
100
  return newDomNode;
79
101
  }
80
102
  if ((_a = domNode.attribs) == null ? void 0 : _a.class) {
81
- if (hasResponsiveStyles) {
82
- domNode.attribs.class = domNode.attribs.class.replace(
83
- /[:#\!\-[\]\/\.%]+/g,
84
- "_"
85
- );
86
- } else {
87
- const currentStyles = domNode.attribs.style ? `${domNode.attribs.style};` : "";
88
- const tailwindStyles = twi(domNode.attribs.class);
89
- domNode.attribs.style = `${currentStyles} ${tailwindStyles}`;
103
+ const cleanRegex = /[:#\!\-[\]\/\.%]+/g;
104
+ const cleanTailwindClasses = domNode.attribs.class.replace(cleanRegex, "_");
105
+ const currentStyles = domNode.attribs.style ? `${domNode.attribs.style};` : "";
106
+ const tailwindStyles = cleanTailwindClasses.split(" ").map((className) => {
107
+ return cssMap[`.${className}`];
108
+ }).join(";");
109
+ domNode.attribs.style = `${currentStyles} ${tailwindStyles}`;
110
+ domNode.attribs.class = domNode.attribs.class.split(" ").filter((className) => {
111
+ const cleanedClassName = className.replace(cleanRegex, "_");
112
+ return className.search(/^.{2}:/) !== -1 || !cssMap[`.${cleanedClassName}`];
113
+ }).join(" ").replace(cleanRegex, "_");
114
+ if (domNode.attribs.class === "")
90
115
  delete domNode.attribs.class;
91
- }
92
116
  }
93
117
  }
94
118
  }
@@ -98,27 +122,48 @@ var Tailwind = ({ children, config }) => {
98
122
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: reactHTML });
99
123
  };
100
124
  Tailwind.displayName = "Tailwind";
101
- function getMediaQueryCSS(css) {
102
- const mediaQueryRegex = /@media[^{]+\{(?<content>[\s\S]+?)\}\s*\}/gm;
103
- let newCss = css.replace(mediaQueryRegex, (m) => {
125
+ function cleanCss(css) {
126
+ let newCss = css.replace(/\\/g, "").replace(/[.\!\#\w\d\\:\-\[\]\/\.%\(\))]+(?=\s*?{[^{]*?\})\s*?{/g, (m) => {
127
+ return m.replace(new RegExp("(?<=.)[:#\\!\\-[\\\\\\]\\/\\.%]+", "g"), "_");
128
+ }).replace(new RegExp("font-family(?<value>[^;\\r\\n]+)", "g"), (m, value) => {
129
+ return `font-family${value.replace(/['"]+/g, "")}`;
130
+ });
131
+ return newCss;
132
+ }
133
+ function getMediaQueryCss(css) {
134
+ var _a, _b;
135
+ const mediaQueryRegex = new RegExp("@media[^{]+\\{(?<content>[\\s\\S]+?)\\}\\s*\\}", "gm");
136
+ return (_b = (_a = css.replace(mediaQueryRegex, (m) => {
104
137
  return m.replace(
105
138
  /([^{]+\{)([\s\S]+?)(\}\s*\})/gm,
106
139
  (_, start, content, end) => {
107
- const newcontent = content.replace(
108
- /(?:[\s\r\n]*)?(?<prop>[\w-]+)\s*:\s*(?<value>[^;\r\n]+)/gm,
140
+ const newContent = content.replace(
141
+ new RegExp("(?:[\\s\\r\\n]*)?(?<prop>[\\w-]+)\\s*:\\s*(?<value>[^};\\r\\n]+)", "gm"),
109
142
  (_2, prop, value) => {
110
- return `${prop}: ${value} !important`;
143
+ return `${prop}: ${value} !important;`;
111
144
  }
112
145
  );
113
- return `${start}${newcontent}${end}`;
146
+ return `${start}${newContent}${end}`;
114
147
  }
115
148
  );
116
- }).replace(/[.\!\#\w\d\\:\-\[\]\/\.%]+\s*?{/g, (m) => {
117
- return m.replace(/(?<=.)[:#\!\-[\\\]\/\.%]+/g, "_");
118
- }).replace(/font-family(?<value>[^;\r\n]+)/g, (m, value) => {
119
- return `font-family${value.replace(/['"]+/g, "")}`;
120
- });
121
- return newCss;
149
+ }).match(/@media\s*([^{]+)\{([^{}]*\{[^{}]*\})*[^{}]*\}/g)) == null ? void 0 : _a.join("")) != null ? _b : "";
150
+ }
151
+ function makeCssMap(css) {
152
+ const cssNoMedia = css.replace(
153
+ new RegExp("@media[^{]+\\{(?<content>[\\s\\S]+?)\\}\\s*\\}", "gm"),
154
+ ""
155
+ );
156
+ const cssMap = cssNoMedia.split("}").reduce(
157
+ (acc, cur) => {
158
+ const [key, value] = cur.split("{");
159
+ if (key && value) {
160
+ acc[key] = value;
161
+ }
162
+ return acc;
163
+ },
164
+ {}
165
+ );
166
+ return cssMap;
122
167
  }
123
168
  // Annotate the CommonJS export names for ESM import in node:
124
169
  0 && (module.exports = {
package/dist/index.mjs CHANGED
@@ -1,27 +1,52 @@
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
+
1
21
  // src/tailwind.tsx
2
22
  import * as React from "react";
3
23
  import { renderToStaticMarkup } from "react-dom/server";
4
- import htmlParser, { attributesToProps, Element } from "html-react-parser";
24
+ import htmlParser, {
25
+ attributesToProps,
26
+ domToReact,
27
+ Element
28
+ } from "html-react-parser";
5
29
  import { tailwindToCSS } from "tw-to-css";
6
- import { Fragment, jsx } from "react/jsx-runtime";
30
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
31
  var Tailwind = ({ children, config }) => {
8
32
  const { twi } = tailwindToCSS({
9
33
  config
10
34
  });
11
35
  const newChildren = React.Children.toArray(children);
12
36
  const fullHTML = renderToStaticMarkup(/* @__PURE__ */ jsx(Fragment, { children: newChildren }));
13
- const headStyle = getMediaQueryCSS(
14
- twi(fullHTML, {
15
- merge: false,
16
- ignoreMediaQueries: false
17
- })
18
- );
19
- const hasResponsiveStyles = /@media[^{]+\{(?<content>[\s\S]+?)\}\s*\}/gm.test(
37
+ const tailwindCss = twi(fullHTML, {
38
+ merge: false,
39
+ ignoreMediaQueries: false
40
+ });
41
+ const css = cleanCss(tailwindCss);
42
+ const cssMap = makeCssMap(css);
43
+ const headStyle = getMediaQueryCss(css);
44
+ const hasResponsiveStyles = new RegExp("@media[^{]+\\{(?<content>[\\s\\S]+?)\\}\\s*\\}", "gm").test(
20
45
  headStyle
21
46
  );
22
47
  const hasHTML = /<html[^>]*>/gm.test(fullHTML);
23
48
  const hasHead = /<head[^>]*>/gm.test(fullHTML);
24
- if (hasResponsiveStyles && !hasHTML && !hasHead) {
49
+ if (hasResponsiveStyles && (!hasHTML || !hasHead)) {
25
50
  throw new Error(
26
51
  "Tailwind: To use responsive styles you must have a <html> and <head> element in your template."
27
52
  );
@@ -37,26 +62,28 @@ var Tailwind = ({ children, config }) => {
37
62
  if (hasResponsiveStyles && hasHead && domNode.name === "head") {
38
63
  let newDomNode = null;
39
64
  if (domNode.children) {
40
- const style = domNode.children.find(
41
- (child2) => child2 instanceof Element && child2.name === "style"
42
- );
43
65
  const props = attributesToProps(domNode.attribs);
44
- newDomNode = /* @__PURE__ */ jsx("head", { ...props, children: style && style instanceof Element ? /* @__PURE__ */ jsx("style", { children: `${style.children} ${headStyle}` }) : /* @__PURE__ */ jsx("style", { children: headStyle }) });
66
+ newDomNode = /* @__PURE__ */ jsxs("head", __spreadProps(__spreadValues({}, props), { children: [
67
+ domToReact(domNode.children),
68
+ /* @__PURE__ */ jsx("style", { children: headStyle })
69
+ ] }));
45
70
  }
46
71
  return newDomNode;
47
72
  }
48
73
  if ((_a = domNode.attribs) == null ? void 0 : _a.class) {
49
- if (hasResponsiveStyles) {
50
- domNode.attribs.class = domNode.attribs.class.replace(
51
- /[:#\!\-[\]\/\.%]+/g,
52
- "_"
53
- );
54
- } else {
55
- const currentStyles = domNode.attribs.style ? `${domNode.attribs.style};` : "";
56
- const tailwindStyles = twi(domNode.attribs.class);
57
- domNode.attribs.style = `${currentStyles} ${tailwindStyles}`;
74
+ const cleanRegex = /[:#\!\-[\]\/\.%]+/g;
75
+ const cleanTailwindClasses = domNode.attribs.class.replace(cleanRegex, "_");
76
+ const currentStyles = domNode.attribs.style ? `${domNode.attribs.style};` : "";
77
+ const tailwindStyles = cleanTailwindClasses.split(" ").map((className) => {
78
+ return cssMap[`.${className}`];
79
+ }).join(";");
80
+ domNode.attribs.style = `${currentStyles} ${tailwindStyles}`;
81
+ domNode.attribs.class = domNode.attribs.class.split(" ").filter((className) => {
82
+ const cleanedClassName = className.replace(cleanRegex, "_");
83
+ return className.search(/^.{2}:/) !== -1 || !cssMap[`.${cleanedClassName}`];
84
+ }).join(" ").replace(cleanRegex, "_");
85
+ if (domNode.attribs.class === "")
58
86
  delete domNode.attribs.class;
59
- }
60
87
  }
61
88
  }
62
89
  }
@@ -66,27 +93,48 @@ var Tailwind = ({ children, config }) => {
66
93
  return /* @__PURE__ */ jsx(Fragment, { children: reactHTML });
67
94
  };
68
95
  Tailwind.displayName = "Tailwind";
69
- function getMediaQueryCSS(css) {
70
- const mediaQueryRegex = /@media[^{]+\{(?<content>[\s\S]+?)\}\s*\}/gm;
71
- let newCss = css.replace(mediaQueryRegex, (m) => {
96
+ function cleanCss(css) {
97
+ let newCss = css.replace(/\\/g, "").replace(/[.\!\#\w\d\\:\-\[\]\/\.%\(\))]+(?=\s*?{[^{]*?\})\s*?{/g, (m) => {
98
+ return m.replace(new RegExp("(?<=.)[:#\\!\\-[\\\\\\]\\/\\.%]+", "g"), "_");
99
+ }).replace(new RegExp("font-family(?<value>[^;\\r\\n]+)", "g"), (m, value) => {
100
+ return `font-family${value.replace(/['"]+/g, "")}`;
101
+ });
102
+ return newCss;
103
+ }
104
+ function getMediaQueryCss(css) {
105
+ var _a, _b;
106
+ const mediaQueryRegex = new RegExp("@media[^{]+\\{(?<content>[\\s\\S]+?)\\}\\s*\\}", "gm");
107
+ return (_b = (_a = css.replace(mediaQueryRegex, (m) => {
72
108
  return m.replace(
73
109
  /([^{]+\{)([\s\S]+?)(\}\s*\})/gm,
74
110
  (_, start, content, end) => {
75
- const newcontent = content.replace(
76
- /(?:[\s\r\n]*)?(?<prop>[\w-]+)\s*:\s*(?<value>[^;\r\n]+)/gm,
111
+ const newContent = content.replace(
112
+ new RegExp("(?:[\\s\\r\\n]*)?(?<prop>[\\w-]+)\\s*:\\s*(?<value>[^};\\r\\n]+)", "gm"),
77
113
  (_2, prop, value) => {
78
- return `${prop}: ${value} !important`;
114
+ return `${prop}: ${value} !important;`;
79
115
  }
80
116
  );
81
- return `${start}${newcontent}${end}`;
117
+ return `${start}${newContent}${end}`;
82
118
  }
83
119
  );
84
- }).replace(/[.\!\#\w\d\\:\-\[\]\/\.%]+\s*?{/g, (m) => {
85
- return m.replace(/(?<=.)[:#\!\-[\\\]\/\.%]+/g, "_");
86
- }).replace(/font-family(?<value>[^;\r\n]+)/g, (m, value) => {
87
- return `font-family${value.replace(/['"]+/g, "")}`;
88
- });
89
- return newCss;
120
+ }).match(/@media\s*([^{]+)\{([^{}]*\{[^{}]*\})*[^{}]*\}/g)) == null ? void 0 : _a.join("")) != null ? _b : "";
121
+ }
122
+ function makeCssMap(css) {
123
+ const cssNoMedia = css.replace(
124
+ new RegExp("@media[^{]+\\{(?<content>[\\s\\S]+?)\\}\\s*\\}", "gm"),
125
+ ""
126
+ );
127
+ const cssMap = cssNoMedia.split("}").reduce(
128
+ (acc, cur) => {
129
+ const [key, value] = cur.split("{");
130
+ if (key && value) {
131
+ acc[key] = value;
132
+ }
133
+ return acc;
134
+ },
135
+ {}
136
+ );
137
+ return cssMap;
90
138
  }
91
139
  export {
92
140
  Tailwind
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-email/tailwind",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "A React component to wrap emails with Tailwind CSS",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -9,6 +9,18 @@
9
9
  "files": [
10
10
  "dist/**"
11
11
  ],
12
+ "exports": {
13
+ ".": {
14
+ "import": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.mjs"
17
+ },
18
+ "require": {
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ }
22
+ }
23
+ },
12
24
  "license": "MIT",
13
25
  "scripts": {
14
26
  "build": "tsup src/index.ts --format esm,cjs --dts --external react",
@@ -17,12 +29,12 @@
17
29
  "clean": "rm -rf dist",
18
30
  "test": "jest",
19
31
  "test:watch": "jest --watch",
20
- "format:check": "prettier --ignore-path ./../../.prettierignore --check \"**/*.{ts,tsx,md}\"",
21
- "format": "prettier --ignore-path ./../../.prettierignore --write \"**/*.{ts,tsx,md}\""
32
+ "format:check": "prettier --check \"**/*.{ts,tsx,md}\"",
33
+ "format": "prettier --write \"**/*.{ts,tsx,md}\""
22
34
  },
23
35
  "repository": {
24
36
  "type": "git",
25
- "url": "https://github.com/zenorocha/react-email.git",
37
+ "url": "https://github.com/resendlabs/react-email.git",
26
38
  "directory": "packages/tailwind"
27
39
  },
28
40
  "keywords": [
@@ -34,21 +46,27 @@
34
46
  "node": ">=16.0.0"
35
47
  },
36
48
  "dependencies": {
37
- "html-react-parser": "3.0.9",
49
+ "html-react-parser": "4.0.0",
38
50
  "react": "18.2.0",
39
51
  "react-dom": "18.2.0",
40
52
  "tw-to-css": "0.0.11"
41
53
  },
42
54
  "devDependencies": {
55
+ "@babel/core": "^7.21.8",
56
+ "@babel/preset-react": "7.22.5",
57
+ "@react-email/button": "0.0.7",
43
58
  "@testing-library/react": "14.0.0",
59
+ "@types/jest": "29.5.3",
44
60
  "@types/react": "18.0.20",
45
61
  "@types/react-dom": "18.0.6",
46
- "eslint": "8.23.1",
47
- "eslint-config-custom": "*",
62
+ "babel-jest": "29.6.1",
63
+ "eslint": "8.45.0",
64
+ "jest": "29.6.1",
65
+ "prettier": "3.0.0",
48
66
  "react": "18.2.0",
49
- "tsconfig": "*",
50
- "tsup": "6.2.3",
51
- "typescript": "4.8.3"
67
+ "ts-jest": "29.1.1",
68
+ "tsup": "7.1.0",
69
+ "typescript": "5.1.6"
52
70
  },
53
71
  "publishConfig": {
54
72
  "access": "public"
package/readme.md CHANGED
@@ -32,8 +32,8 @@ npm install @react-email/tailwind -E
32
32
  Add the component around your email body content.
33
33
 
34
34
  ```jsx
35
- import { Button } from '@react-email/button';
36
- import { Tailwind } from '@react-email/tailwind';
35
+ import { Button } from "@react-email/button";
36
+ import { Tailwind } from "@react-email/tailwind";
37
37
 
38
38
  const Email = () => {
39
39
  return (
@@ -42,7 +42,7 @@ const Email = () => {
42
42
  theme: {
43
43
  extend: {
44
44
  colors: {
45
- 'custom-color': '#ff0000',
45
+ "custom-color": "#ff0000",
46
46
  },
47
47
  },
48
48
  },
@@ -65,7 +65,7 @@ This component was tested using the most popular email clients.
65
65
 
66
66
  | <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"> |
67
67
  | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
68
- | Gmail ✔ | Apple Mail ✔ | Outlook ✔ | Yahoo! Mail ✔ | HEY ✔ | Superhuman ✔ |
68
+ | Gmail ✔ | Apple Mail ✔ | Outlook ✔ | Yahoo! Mail ✔ | HEY ✔ | Superhuman ✔ |
69
69
 
70
70
  ## License
71
71