diff --git a/.github/workflows/percy-issue-comment.yml b/.github/workflows/percy-issue-comment.yml
index adad348a9827a36b6681939e3517c961dbdaeaf9..5407755699fd6619c652ac83d851520fbaab470d 100644
--- a/.github/workflows/percy-issue-comment.yml
+++ b/.github/workflows/percy-issue-comment.yml
@@ -52,7 +52,7 @@ jobs:
         uses: actions/setup-java@v2
         with:
           java-version: 8
-          distribution: 'temurin'
+          distribution: "temurin"
       - name: Install Clojure CLI
         run: |
           curl -O https://download.clojure.org/install/linux-install-1.10.3.933.sh &&
@@ -116,7 +116,7 @@ jobs:
         uses: actions/setup-java@v2
         with:
           java-version: 8
-          distribution: 'temurin'
+          distribution: "temurin"
       - name: Install Clojure CLI
         run: |
           curl -O https://download.clojure.org/install/linux-install-1.10.3.933.sh &&
@@ -132,7 +132,6 @@ jobs:
           path: ~/.cache/yarn
           key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
       - run: yarn install --frozen-lockfile --prefer-offline
-
       - uses: actions/download-artifact@v2
         name: Retrieve uberjar artifact
         with:
@@ -141,7 +140,8 @@ jobs:
         run: |
           jar xf target/uberjar/metabase.jar version.properties
           mv version.properties resources/
-
+      - name: Run maildev
+        run: docker run -d -p 80:80 -p 25:25 maildev/maildev
       - name: Percy Test
         run: yarn run test-visual-no-build
         env:
diff --git a/.github/workflows/percy.yml b/.github/workflows/percy.yml
index a825ed2f4947aec459f8d2d3309470ea77bc018f..d933710ab870fccdf13ec5bb6a446885339c6faf 100644
--- a/.github/workflows/percy.yml
+++ b/.github/workflows/percy.yml
@@ -33,7 +33,7 @@ jobs:
         uses: actions/setup-java@v2
         with:
           java-version: 8
-          distribution: 'temurin'
+          distribution: "temurin"
       - name: Install Clojure CLI
         run: |
           curl -O https://download.clojure.org/install/linux-install-1.10.3.933.sh &&
@@ -90,7 +90,7 @@ jobs:
         uses: actions/setup-java@v2
         with:
           java-version: 8
-          distribution: 'temurin'
+          distribution: "temurin"
       - name: Install Clojure CLI
         run: |
           curl -O https://download.clojure.org/install/linux-install-1.10.3.933.sh &&
@@ -115,7 +115,8 @@ jobs:
         run: |
           jar xf target/uberjar/metabase.jar version.properties
           mv version.properties resources/
-
+      - name: Run maildev
+        run: docker run -d -p 80:80 -p 25:25 maildev/maildev
       - name: Percy Test
         run: yarn run test-visual-no-build
         env:
diff --git a/frontend/test/__support__/e2e/commands.js b/frontend/test/__support__/e2e/commands.js
index be5edc0f50cd87cdbcb727beea925e8e52033600..8cab51878a4fcd77db2c8faedfcb68ee2b500d4b 100644
--- a/frontend/test/__support__/e2e/commands.js
+++ b/frontend/test/__support__/e2e/commands.js
@@ -13,6 +13,7 @@ import "./commands/api/user";
 
 import "./commands/api/composite/createQuestionAndDashboard";
 import "./commands/api/composite/createNativeQuestionAndDashboard";
+import "./commands/api/composite/createQuestionAndAddToDashboard";
 
 import "./commands/user/createUser";
 import "./commands/user/authentication";
diff --git a/frontend/test/__support__/e2e/commands/api/composite/createQuestionAndAddToDashboard.js b/frontend/test/__support__/e2e/commands/api/composite/createQuestionAndAddToDashboard.js
new file mode 100644
index 0000000000000000000000000000000000000000..f808d8c6f9fe29d670d0b68fa9730c52b3f782d9
--- /dev/null
+++ b/frontend/test/__support__/e2e/commands/api/composite/createQuestionAndAddToDashboard.js
@@ -0,0 +1,10 @@
+Cypress.Commands.add(
+  "createQuestionAndAddToDashboard",
+  (query, dashboardId) => {
+    return cy.createQuestion(query).then(response => {
+      cy.request("POST", `/api/dashboard/${dashboardId}/cards`, {
+        cardId: response.body.id,
+      });
+    });
+  },
+);
diff --git a/frontend/test/__support__/e2e/commands/ui/button.js b/frontend/test/__support__/e2e/commands/ui/button.js
index 016556399fce2935c0b5409531c06da414ad96ca..1b28b092ea94236723709ab101b3e7c5c134fb79 100644
--- a/frontend/test/__support__/e2e/commands/ui/button.js
+++ b/frontend/test/__support__/e2e/commands/ui/button.js
@@ -1,3 +1,3 @@
-Cypress.Commands.add("button", button_name => {
-  cy.findByRole("button", { name: button_name });
+Cypress.Commands.add("button", (button_name, timeout) => {
+  cy.findByRole("button", { name: button_name, timeout: timeout });
 });
diff --git a/frontend/test/__support__/e2e/cypress.json b/frontend/test/__support__/e2e/cypress.json
index 7efc9abc53bf615c90be7c6ade4996de6b2df943..ed9299a277ce958d0c7e5bd2f3acefcb03e48b39 100644
--- a/frontend/test/__support__/e2e/cypress.json
+++ b/frontend/test/__support__/e2e/cypress.json
@@ -4,6 +4,7 @@
   "integrationFolder": ".",
   "supportFile": "frontend/test/__support__/e2e/cypress.js",
   "videoUploadOnPasses": false,
+  "chromeWebSecurity": false,
   "viewportHeight": 800,
   "viewportWidth": 1280,
   "retries": {
diff --git a/frontend/test/__support__/e2e/helpers/e2e-misc-helpers.js b/frontend/test/__support__/e2e/helpers/e2e-misc-helpers.js
index 346aadbd5821e668c96b60e35b2162b040aa936a..35a56298856ed25bc656ded0f5fdc92733de9785 100644
--- a/frontend/test/__support__/e2e/helpers/e2e-misc-helpers.js
+++ b/frontend/test/__support__/e2e/helpers/e2e-misc-helpers.js
@@ -94,3 +94,52 @@ export function interceptPromise(method, path) {
   });
   return state;
 }
+
+const chainStart = Symbol();
+
+/**
+ * Waits for all Cypress commands similarly to Promise.all.
+ * Helps to avoid excessive nesting and verbosity
+ *
+ * @param {Array.<Cypress.Chainable<any>>} commands - Cypress commands
+ * @example
+ * cypressWaitAll([
+ *   cy.createQuestionAndAddToDashboard(firstQuery, 1),
+ *   cy.createQuestionAndAddToDashboard(secondQuery, 1),
+ * ]).then(() => {
+ *   cy.visit(`/dashboard/1`);
+ * });
+ */
+export const cypressWaitAll = function(commands) {
+  const _ = Cypress._;
+  const chain = cy.wrap(null, { log: false });
+
+  const stopCommand = _.find(cy.queue.commands, {
+    attributes: { chainerId: chain.chainerId },
+  });
+
+  const startCommand = _.find(cy.queue.commands, {
+    attributes: { chainerId: commands[0].chainerId },
+  });
+
+  const p = chain.then(() => {
+    return _(commands)
+      .map(cmd => {
+        return cmd[chainStart]
+          ? cmd[chainStart].attributes
+          : _.find(cy.queue.commands, {
+              attributes: { chainerId: cmd.chainerId },
+            }).attributes;
+      })
+      .concat(stopCommand.attributes)
+      .slice(1)
+      .flatMap(cmd => {
+        return cmd.prev.get("subject");
+      })
+      .value();
+  });
+
+  p[chainStart] = startCommand;
+
+  return p;
+};
diff --git a/frontend/test/metabase-visual/static-visualizations/static-visualizations.cy.spec.js b/frontend/test/metabase-visual/static-visualizations/static-visualizations.cy.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..427fc68b57fdb7a0b60f360ad777bb6b547206f4
--- /dev/null
+++ b/frontend/test/metabase-visual/static-visualizations/static-visualizations.cy.spec.js
@@ -0,0 +1,105 @@
+import { restore, setupSMTP, cypressWaitAll } from "__support__/e2e/cypress";
+import { USERS } from "__support__/e2e/cypress_data";
+import { SAMPLE_DATASET } from "__support__/e2e/cypress_sample_dataset";
+
+const { ORDERS_ID, ORDERS, PRODUCTS } = SAMPLE_DATASET;
+
+const { admin } = USERS;
+
+const visualizationTypes = ["line", "area", "bar", "combo"];
+
+const SENDING_EMAIL_TIMEOUT = 30000;
+
+describe("static visualizations", () => {
+  beforeEach(() => {
+    restore();
+    cy.signInAsAdmin();
+    setupSMTP();
+  });
+
+  visualizationTypes.map(type => {
+    it(`${type} chart`, () => {
+      const dashboardName = `${type} charts dashboard`;
+      cy.createDashboard({ name: dashboardName })
+        .then(({ body: { id: dashboardId } }) => {
+          return cypressWaitAll([
+            createOneMetricTwoDimensionsQuestion(type, dashboardId),
+            createOneDimensionTwoMetricsQuestion(type, dashboardId),
+          ]).then(() => {
+            cy.visit(`/dashboard/${dashboardId}`);
+          });
+        })
+        .then(() => {
+          cy.icon("share").click();
+          cy.findByText("Dashboard subscriptions").click();
+
+          cy.findByText("Email it").click();
+          cy.findByPlaceholderText("Enter user names or email addresses")
+            .click()
+            .type(`${admin.first_name} ${admin.last_name}{enter}`)
+            .blur();
+
+          cy.button("Send email now").click();
+          cy.button("Email sent", SENDING_EMAIL_TIMEOUT);
+
+          openEmailPage(dashboardName).then(() => {
+            cy.percySnapshot();
+          });
+        });
+    });
+  });
+});
+
+function createOneDimensionTwoMetricsQuestion(display, dashboardId) {
+  return cy.createQuestionAndAddToDashboard(
+    {
+      name: `${display} one dimension two metrics`,
+      query: {
+        "source-table": ORDERS_ID,
+        aggregation: [["count"], ["avg", ["field", ORDERS.TOTAL, null]]],
+        breakout: [["field", ORDERS.CREATED_AT, { "temporal-unit": "month" }]],
+      },
+      visualization_settings: {
+        "graph.dimensions": ["CREATED_AT"],
+        "graph.metrics": ["count", "avg"],
+      },
+      display: display,
+      database: 1,
+    },
+    dashboardId,
+  );
+}
+
+function createOneMetricTwoDimensionsQuestion(display, dashboardId) {
+  return cy.createQuestionAndAddToDashboard(
+    {
+      name: `${display} one metric two dimensions`,
+      query: {
+        "source-table": ORDERS_ID,
+        aggregation: [["count"]],
+        breakout: [
+          ["field", ORDERS.CREATED_AT, { "temporal-unit": "month" }],
+          ["field", PRODUCTS.CATEGORY, { "source-field": ORDERS.PRODUCT_ID }],
+        ],
+      },
+      visualization_settings: {
+        "graph.dimensions": ["CREATED_AT", "CATEGORY"],
+        "graph.metrics": ["count"],
+      },
+      display: display,
+      database: 1,
+    },
+    dashboardId,
+  );
+}
+
+function openEmailPage(emailSubject) {
+  cy.window().then(win => (win.location.href = "http://localhost"));
+  cy.findByText(emailSubject).click();
+
+  return cy.hash().then(path => {
+    const htmlPath = `http://localhost${path.slice(1)}/html`;
+    cy.window().then(win => (win.location.href = htmlPath));
+    cy.findByText(emailSubject);
+  });
+}