diff --git a/config/runtime.exs b/config/runtime.exs
index f31c2bc41b047ccb882e1304713d623910512246..103537cd509eddb12ac8edd5179479db7f5375ae 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -377,6 +377,14 @@ config :kaffy,
       resources: [
         site: [schema: Plausible.Site, admin: Plausible.SiteAdmin]
       ]
+    ],
+    billing: [
+      resources: [
+        enterprise_plan: [
+          schema: Plausible.Billing.EnterprisePlan,
+          admin: Plausible.Billing.EnterprisePlanAdmin
+        ]
+      ]
     ]
   ]
 
diff --git a/lib/plausible/auth/user.ex b/lib/plausible/auth/user.ex
index a367672b0ebe7cd57e06fa521c002b08be2488a4..5c0fe34cc37cd3c54eacbc287fb949c19c58eef5 100644
--- a/lib/plausible/auth/user.ex
+++ b/lib/plausible/auth/user.ex
@@ -25,6 +25,7 @@ defmodule Plausible.Auth.User do
     has_many :api_keys, Plausible.Auth.ApiKey
     has_one :google_auth, Plausible.Site.GoogleAuth
     has_one :subscription, Plausible.Billing.Subscription
+    has_one :enterprise_plan, Plausible.Billing.EnterprisePlan
 
     timestamps()
   end
diff --git a/lib/plausible/billing/enterprise_plan.ex b/lib/plausible/billing/enterprise_plan.ex
new file mode 100644
index 0000000000000000000000000000000000000000..65141098323636619f0af1119bb7b296a48296e1
--- /dev/null
+++ b/lib/plausible/billing/enterprise_plan.ex
@@ -0,0 +1,30 @@
+defmodule Plausible.Billing.EnterprisePlan do
+  use Ecto.Schema
+  import Ecto.Changeset
+
+  @required_fields [
+    :user_id,
+    :paddle_plan_id,
+    :billing_interval,
+    :monthly_pageview_limit,
+    :hourly_api_request_limit
+  ]
+
+  schema "enterprise_plans" do
+    field :paddle_plan_id, :string
+    field :billing_interval, Ecto.Enum, values: [:monthly, :yearly]
+    field :monthly_pageview_limit, :integer
+    field :hourly_api_request_limit, :integer
+
+    belongs_to :user, Plausible.Auth.User
+
+    timestamps()
+  end
+
+  def changeset(model, attrs \\ %{}) do
+    model
+    |> cast(attrs, @required_fields)
+    |> validate_required(@required_fields)
+    |> unique_constraint(:user_id)
+  end
+end
diff --git a/lib/plausible/billing/enterprise_plan_admin.ex b/lib/plausible/billing/enterprise_plan_admin.ex
new file mode 100644
index 0000000000000000000000000000000000000000..616eea5b66a055d6ea2d30eaf7e5bc21f1d4b227
--- /dev/null
+++ b/lib/plausible/billing/enterprise_plan_admin.ex
@@ -0,0 +1,37 @@
+defmodule Plausible.Billing.EnterprisePlanAdmin do
+  use Plausible.Repo
+
+  def search_fields(_schema) do
+    [
+      :paddle_plan_id,
+      user: [:name, :email]
+    ]
+  end
+
+  def form_fields(_) do
+    [
+      user_id: nil,
+      paddle_plan_id: nil,
+      billing_interval: %{choices: [{"Yearly", "yearly"}, {"Monthly", "monthly"}]},
+      monthly_pageview_limit: nil,
+      hourly_api_request_limit: nil
+    ]
+  end
+
+  def custom_index_query(_conn, _schema, query) do
+    from(r in query, preload: :user)
+  end
+
+  def index(_) do
+    [
+      id: nil,
+      user_email: %{value: &get_user_email/1},
+      paddle_plan_id: nil,
+      billing_interval: nil,
+      monthly_pageview_limit: nil,
+      hourly_api_request_limit: nil
+    ]
+  end
+
+  defp get_user_email(plan), do: plan.user.email
+end
diff --git a/lib/plausible/billing/plans.ex b/lib/plausible/billing/plans.ex
index 7cc0895a41a38fdaaf85c93352630334d92b6913..9f7d7062a6cd3c586eab483a0064c763dbe53cfc 100644
--- a/lib/plausible/billing/plans.ex
+++ b/lib/plausible/billing/plans.ex
@@ -1,4 +1,6 @@
 defmodule Plausible.Billing.Plans do
+  use Plausible.Repo
+
   @unlisted_plans_v1 [
     %{limit: 150_000_000, yearly_product_id: "648089", yearly_cost: "$4800"}
   ]
@@ -30,21 +32,15 @@ defmodule Plausible.Billing.Plans do
     end)
   end
 
-  def subscription_quota("free_10k"), do: "10k"
-
-  def subscription_quota(product_id) do
-    case for_product_id(product_id) do
-      nil -> raise "Unknown quota for subscription #{product_id}"
-      product -> number_format(product[:limit])
-    end
-  end
-
   def subscription_interval("free_10k"), do: "N/A"
 
   def subscription_interval(product_id) do
     case for_product_id(product_id) do
       nil ->
-        raise "Unknown interval for subscription #{product_id}"
+        enterprise_plan =
+          Repo.get_by(Plausible.Billing.EnterprisePlan, paddle_plan_id: product_id)
+
+        enterprise_plan && enterprise_plan.billing_interval
 
       plan ->
         if product_id == plan[:monthly_product_id] do
@@ -62,6 +58,11 @@ defmodule Plausible.Billing.Plans do
 
     if found do
       Map.fetch!(found, :limit)
+    else
+      enterprise_plan =
+        Repo.get_by(Plausible.Billing.EnterprisePlan, paddle_plan_id: subscription.paddle_plan_id)
+
+      enterprise_plan && enterprise_plan.monthly_pageview_limit
     end
   end
 
diff --git a/lib/plausible/mailer.ex b/lib/plausible/mailer.ex
index 83fd6ba80e3ca49ee5f412a5975f3b549b335ffb..8768e542a3a7c39b08cde31142766a39ac6fbe54 100644
--- a/lib/plausible/mailer.ex
+++ b/lib/plausible/mailer.ex
@@ -14,4 +14,16 @@ defmodule Plausible.Mailer do
         reraise error, __STACKTRACE__
     end
   end
+
+  def send_email_safe(email) do
+    try do
+      Plausible.Mailer.deliver_now!(email)
+    rescue
+      error ->
+        Sentry.capture_exception(error,
+          stacktrace: __STACKTRACE__,
+          extra: %{extra: "Error while sending email"}
+        )
+    end
+  end
 end
diff --git a/lib/plausible_web/controllers/billing_controller.ex b/lib/plausible_web/controllers/billing_controller.ex
index 5de38f183a8fdb03f23075bc1865b62111a70627..7b81f276fa186dfc1094b4cda5b7eeddfeadb580 100644
--- a/lib/plausible_web/controllers/billing_controller.ex
+++ b/lib/plausible_web/controllers/billing_controller.ex
@@ -6,20 +6,122 @@ defmodule PlausibleWeb.BillingController do
 
   plug PlausibleWeb.RequireAccountPlug
 
-  def admin_email do
-    Application.get_env(:plausible, :admin_email)
+  def upgrade(conn, _params) do
+    user =
+      conn.assigns[:current_user]
+      |> Repo.preload(:enterprise_plan)
+
+    cond do
+      user.subscription && user.subscription.status == "active" ->
+        redirect(conn, to: Routes.billing_path(conn, :change_plan_form))
+
+      user.enterprise_plan ->
+        redirect(conn,
+          to: Routes.billing_path(conn, :upgrade_enterprise_plan, user.enterprise_plan.id)
+        )
+
+      true ->
+        render(conn, "upgrade.html",
+          usage: Plausible.Billing.usage(user),
+          user: user,
+          layout: {PlausibleWeb.LayoutView, "focus.html"}
+        )
+    end
   end
 
-  def change_plan_form(conn, _params) do
-    subscription = Billing.active_subscription_for(conn.assigns[:current_user].id)
+  def upgrade_enterprise_plan(conn, %{"plan_id" => plan_id}) do
+    user =
+      conn.assigns[:current_user]
+      |> Repo.preload(:enterprise_plan)
 
-    if subscription do
-      render(conn, "change_plan.html",
-        subscription: subscription,
+    if user.enterprise_plan && user.enterprise_plan.id == String.to_integer(plan_id) do
+      usage = Plausible.Billing.usage(conn.assigns[:current_user])
+
+      render(conn, "upgrade_to_plan.html",
+        usage: usage,
+        user: user,
         layout: {PlausibleWeb.LayoutView, "focus.html"}
       )
     else
-      redirect(conn, to: "/billing/upgrade")
+      render_error(conn, 404)
+    end
+  end
+
+  def upgrade_to_plan(conn, %{"plan_id" => plan_id}) do
+    plan = Plausible.Billing.Plans.for_product_id(plan_id)
+
+    if plan do
+      cycle = if plan[:monthly_product_id] == plan_id, do: "monthly", else: "yearly"
+      plan = Map.merge(plan, %{cycle: cycle, product_id: plan_id})
+      usage = Plausible.Billing.usage(conn.assigns[:current_user])
+
+      render(conn, "upgrade_to_plan.html",
+        usage: usage,
+        plan: plan,
+        user: conn.assigns[:current_user],
+        layout: {PlausibleWeb.LayoutView, "focus.html"}
+      )
+    else
+      render_error(conn, 404)
+    end
+  end
+
+  def upgrade_success(conn, _params) do
+    render(conn, "upgrade_success.html", layout: {PlausibleWeb.LayoutView, "focus.html"})
+  end
+
+  def change_plan_form(conn, _params) do
+    user =
+      conn.assigns[:current_user]
+      |> Repo.preload(:enterprise_plan)
+
+    subscription = Billing.active_subscription_for(user.id)
+
+    cond do
+      subscription && user.enterprise_plan ->
+        redirect(conn,
+          to: Routes.billing_path(conn, :change_enterprise_plan, user.enterprise_plan.id)
+        )
+
+      subscription ->
+        render(conn, "change_plan.html",
+          subscription: subscription,
+          layout: {PlausibleWeb.LayoutView, "focus.html"}
+        )
+
+      true ->
+        redirect(conn, to: Routes.billing_path(conn, :upgrade))
+    end
+  end
+
+  def change_enterprise_plan(conn, %{"plan_id" => plan_id}) do
+    user =
+      conn.assigns[:current_user]
+      |> Repo.preload(:enterprise_plan)
+
+    cond do
+      is_nil(user.subscription) ->
+        redirect(conn, to: "/billing/upgrade")
+
+      is_nil(user.enterprise_plan) ->
+        render_error(conn, 404)
+
+      user.enterprise_plan.id !== String.to_integer(plan_id) ->
+        render_error(conn, 404)
+
+      user.enterprise_plan.paddle_plan_id == user.subscription.paddle_plan_id ->
+        render(conn, "change_enterprise_plan_contact_us.html",
+          user: user,
+          plan: user.enterprise_plan,
+          layout: {PlausibleWeb.LayoutView, "focus.html"}
+        )
+
+      true ->
+        render(conn, "change_enterprise_plan.html",
+          user: user,
+          plan: user.enterprise_plan,
+          layout: {PlausibleWeb.LayoutView, "focus.html"}
+        )
     end
   end
 
@@ -77,39 +179,4 @@ defmodule PlausibleWeb.BillingController do
         |> redirect(to: "/settings")
     end
   end
-
-  def upgrade(conn, _params) do
-    usage = Plausible.Billing.usage(conn.assigns[:current_user])
-    today = Timex.today()
-
-    render(conn, "upgrade.html",
-      usage: usage,
-      today: today,
-      user: conn.assigns[:current_user],
-      layout: {PlausibleWeb.LayoutView, "focus.html"}
-    )
-  end
-
-  def upgrade_to_plan(conn, %{"plan_id" => plan_id}) do
-    plan = Plausible.Billing.Plans.for_product_id(plan_id)
-
-    if plan do
-      cycle = if plan[:monthly_product_id] == plan_id, do: "monthly", else: "yearly"
-      plan = Map.merge(plan, %{cycle: cycle, product_id: plan_id})
-      usage = Plausible.Billing.usage(conn.assigns[:current_user])
-
-      render(conn, "upgrade_to_plan.html",
-        usage: usage,
-        plan: plan,
-        user: conn.assigns[:current_user],
-        layout: {PlausibleWeb.LayoutView, "focus.html"}
-      )
-    else
-      render_error(conn, 404)
-    end
-  end
-
-  def upgrade_success(conn, _params) do
-    render(conn, "upgrade_success.html", layout: {PlausibleWeb.LayoutView, "focus.html"})
-  end
 end
diff --git a/lib/plausible_web/email.ex b/lib/plausible_web/email.ex
index d21b1008c05f8b2e71c8775b64ac736926a61457..d2f6fee7bb9f2b269d2ecfdb43aea00449d93647 100644
--- a/lib/plausible_web/email.ex
+++ b/lib/plausible_web/email.ex
@@ -119,7 +119,7 @@ defmodule PlausibleWeb.Email do
     base_email()
     |> to(user)
     |> tag("over-limit")
-    |> subject("You have outgrown your Plausible subscription tier ")
+    |> subject("You have outgrown your Plausible subscription tier")
     |> render("over_limit.html", %{
       user: user,
       usage: usage,
@@ -128,6 +128,18 @@ defmodule PlausibleWeb.Email do
     })
   end
 
+  def enterprise_over_limit_email(user, usage, last_cycle) do
+    base_email()
+    |> to("enterprise@plausible.io")
+    |> tag("enterprise-over-limit")
+    |> subject("#{user.email} has outgrown their enterprise plan")
+    |> render("enterprise_over_limit.html", %{
+      user: user,
+      usage: usage,
+      last_cycle: last_cycle
+    })
+  end
+
   def yearly_renewal_notification(user) do
     date = Timex.format!(user.subscription.next_bill_date, "{Mfull} {D}, {YYYY}")
 
diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex
index 158da85013efa4d74173612d4fadbd581b5d89ab..233a814ce7dd7f8a8de215f23937b7efebcf4aef 100644
--- a/lib/plausible_web/router.ex
+++ b/lib/plausible_web/router.ex
@@ -147,6 +147,8 @@ defmodule PlausibleWeb.Router do
     post "/billing/change-plan/:new_plan_id", BillingController, :change_plan
     get "/billing/upgrade", BillingController, :upgrade
     get "/billing/upgrade/:plan_id", BillingController, :upgrade_to_plan
+    get "/billing/upgrade/enterprise/:plan_id", BillingController, :upgrade_enterprise_plan
+    get "/billing/change-plan/enterprise/:plan_id", BillingController, :change_enterprise_plan
     get "/billing/upgrade-success", BillingController, :upgrade_success
 
     get "/sites", SiteController, :index
diff --git a/lib/plausible_web/templates/billing/change_enterprise_plan.html.eex b/lib/plausible_web/templates/billing/change_enterprise_plan.html.eex
new file mode 100644
index 0000000000000000000000000000000000000000..81318c05b4c9bdd038edefb392a5049b951f0100
--- /dev/null
+++ b/lib/plausible_web/templates/billing/change_enterprise_plan.html.eex
@@ -0,0 +1,68 @@
+<script type="text/javascript"  src="https://cdnjs.cloudflare.com/ajax/libs/fetch-jsonp/1.1.3/fetch-jsonp.min.js"></script>
+<div class="mx-auto mt-6 text-center">
+  <h1 class="text-3xl font-black dark:text-gray-100">Change subscription plan</h1>
+</div>
+
+<script>
+  plan = function() {
+    return {
+      localizedPlan: null,
+      price() {
+        var currency = {
+          'USD': '$',
+          'EUR': '€',
+          'GBP': '£'
+        }[this.localizedPlan.currency]
+
+        return currency + this.localizedPlan.price.net
+      },
+      fetchPlan() {
+        fetchJsonp('https://checkout.paddle.com/api/2.0/prices?product_ids=<%= @plan.paddle_plan_id %>')
+          .then(res => res.json())
+          .then((data) => {
+            this.localizedPlan = data.response.products[0]
+          })
+      }
+    }
+  }
+</script>
+
+<div class="w-full max-w-lg px-4 mx-auto mt-4">
+  <div x-init="fetchPlan()" x-data="window.plan()" class="flex-1 p-8 mt-8 bg-white rounded shadow-md dark:bg-gray-800">
+    <div x-show="!localizedPlan" class="mx-auto my-40 loading sm"><div></div></div>
+    <template x-if="localizedPlan">
+      <div>
+        <div class="w-full pb-4 dark:text-gray-100">
+          <span>We've prepared your account for an upgrade to custom limits outside the listed plans:</span>
+        </div>
+
+        <ul class="w-full py-4 dark:text-gray-100">
+          <li>Up to <b><%= PlausibleWeb.StatsView.large_number_format(@plan.monthly_pageview_limit) %></b> monthly pageviews</li>
+          <li>Up to <b><%= PlausibleWeb.StatsView.large_number_format(@plan.hourly_api_request_limit) %></b> hourly api requests</li>
+        </ul>
+
+        <ul class="w-full py-4 dark:text-gray-100">
+          <span>The plan is priced at</span>
+          <template x-if="localizedPlan"><b x-text="price()"></b> </template>
+          <span>per <%= if @plan.billing_interval == :yearly, do: "year", else: "month" %>. On the next page, our payment provider will calculate the prorated amount that your card will be charged if you decide to upgrade now.</span>
+        </ul>
+
+        <div class="mt-6 text-left">
+          <span class="inline-flex w-full rounded-md shadow-sm">
+            <%= link(to: Routes.billing_path(@conn, :change_plan_preview, @plan.paddle_plan_id), class: "inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent leading-5 rounded-md hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:ring active:bg-indigo-700 transition ease-in-out duration-150 ") do %>
+              <svg fill="currentColor" viewBox="0 0 20 20" class="inline w-4 h-4 mr-2"><path d="M10 12a2 2 0 100-4 2 2 0 000 4z"></path><path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"></path></svg>
+              Preview changes
+            <% end %>
+          </span>
+        </div>
+      </div>
+    </template>
+  </div>
+</div>
+
+<div class="mt-8 text-center dark:text-gray-100">
+  Questions? Contact <%= link("support@plausible.io", to: "mailto: support@plausible.io", class: "text-indigo-500") %>
+</div>
+
+<script type="text/javascript" src="https://cdn.paddle.com/paddle/paddle.js"></script>
+<script>Paddle.Setup({vendor: 49430})</script>
diff --git a/lib/plausible_web/templates/billing/change_enterprise_plan_contact_us.html.eex b/lib/plausible_web/templates/billing/change_enterprise_plan_contact_us.html.eex
new file mode 100644
index 0000000000000000000000000000000000000000..67a85d883de2dc927f6daa6c3b7c22971f20c153
--- /dev/null
+++ b/lib/plausible_web/templates/billing/change_enterprise_plan_contact_us.html.eex
@@ -0,0 +1,16 @@
+<div class="mx-auto mt-6 text-center">
+  <h1 class="text-3xl font-black dark:text-gray-100">Change subscription plan</h1>
+</div>
+
+<div class="w-full max-w-lg px-4 mx-auto mt-4">
+  <div class="flex-1 p-8 mt-8 bg-white rounded shadow-md dark:bg-gray-800">
+    <div class="w-full pb-4 dark:text-gray-100">
+      <span>Need to change your limits?</span>
+    </div>
+
+    <ul class="w-full py-4 dark:text-gray-100">
+      <span>Your account is on an enterprise plan. If you want to increase or decrease the limits on your account, please contact us at enterprise@plausible.io</span>
+    </ul>
+
+  </div>
+</div>
diff --git a/lib/plausible_web/templates/billing/change_plan_preview.html.eex b/lib/plausible_web/templates/billing/change_plan_preview.html.eex
index 8622e19aa281bafcd766dd254ac9d726682f9307..98cf7e215c6da616030955a10e0c2a9c951cf6c6 100644
--- a/lib/plausible_web/templates/billing/change_plan_preview.html.eex
+++ b/lib/plausible_web/templates/billing/change_plan_preview.html.eex
@@ -36,7 +36,7 @@
 
     <div class="pt-6"></div>
 
-    <div class="py-2 text-lg font-bold">Next payment</div>
+    <div class="py-4 dark:text-gray-100 text-lg font-bold">Next payment</div>
 
 
     <div class="flex flex-col">
diff --git a/lib/plausible_web/templates/billing/upgrade.html.eex b/lib/plausible_web/templates/billing/upgrade.html.eex
index 569e383ba6edb92227ffc1c857590aef08450555..c2d9c4e8273916de9a55179bf9a40a55c243999a 100644
--- a/lib/plausible_web/templates/billing/upgrade.html.eex
+++ b/lib/plausible_web/templates/billing/upgrade.html.eex
@@ -25,7 +25,6 @@
           'GBP': '£'
         }[plan.currency]
 
-        console.log(plan)
         return currency + plan.price.net
       },
       fetchPlans() {
diff --git a/lib/plausible_web/templates/billing/upgrade_enterprise_plan.html.eex b/lib/plausible_web/templates/billing/upgrade_enterprise_plan.html.eex
new file mode 100644
index 0000000000000000000000000000000000000000..7167f01a17e2041b45f01890b01868be09942d08
--- /dev/null
+++ b/lib/plausible_web/templates/billing/upgrade_enterprise_plan.html.eex
@@ -0,0 +1,44 @@
+<div class="mx-auto mt-6 text-center">
+  <h1 class="text-3xl font-black dark:text-gray-100">Upgrade your free trial</h1>
+</div>
+
+<div>
+  <div class="flex flex-col w-full max-w-4xl px-4 mx-auto mt-4 md:flex-row">
+    <div class="flex-1 px-8 py-4 mt-8 mb-4 bg-white rounded shadow-md dark:bg-gray-800">
+      <div class="w-full py-4 dark:text-gray-100">
+        <span>You've used <b><%= PlausibleWeb.AuthView.delimit_integer(@usage) %></b> billable pageviews in the last 30 days</span>
+      </div>
+
+      <div class="w-full py-4 dark:text-gray-100">
+        <span>With this link you can upgrade to an enterprise plan with <b><%= PlausibleWeb.StatsView.large_number_format(@plan[:limit]) %> monthly pageviews</b></span>, billed on a <%= @plan[:cycle] %> basis.
+      </div>
+
+      <div class="mt-6 text-left">
+        <span class="inline-flex w-full rounded-md shadow-sm">
+          <button type="button" data-theme="none" data-product="<%= @plan[:product_id] %>" data-email="<%= @conn.assigns[:current_user].email %>" data-disable-logout="true" data-passthrough="<%= @conn.assigns[:current_user].id %>" data-success="/billing/upgrade-success" class="items-center button paddle_button">
+            <svg fill="currentColor" viewBox="0 0 20 20" class="inline w-4 h-4 mr-2"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path></svg>
+            Pay securely via Paddle
+          </button>
+        </span>
+      </div>
+    </div>
+
+    <div class="flex-1 pl-8 pt-14">
+      <h3 class="text-lg font-medium text-gray-900 leading-6 dark:text-gray-100">
+         What happens if I go over my page views limit?
+      </h3>
+      <div class="mt-2 text-base text-gray-500 leading-6 dark:text-gray-200">
+        You will never be charged extra for an occasional traffic spike. There are no surprise fees and your card will never be charged unexpectedly.<br /><br />
+        If your page views exceed your plan for two consecutive months, we will contact you to upgrade to a higher plan for the following month. You will have two weeks to make a decision. You can decide to continue with a higher plan or to cancel your account at that point.
+      </div>
+
+    </div>
+  </div>
+</div>
+
+<div class="mt-8 text-center dark:text-gray-100">
+  Questions? Contact <%= link("support@plausible.io", to: "mailto: support@plausible.io", class: "text-indigo-500") %>
+</div>
+
+<script type="text/javascript" src="https://cdn.paddle.com/paddle/paddle.js"></script>
+<script>Paddle.Setup({vendor: 49430})</script>
diff --git a/lib/plausible_web/templates/billing/upgrade_to_plan.html.eex b/lib/plausible_web/templates/billing/upgrade_to_plan.html.eex
index 3adcd89023f50cb403a834df4dedc032344185fe..f0340668ba2d897cda6c14aebb92dc45f3f9950e 100644
--- a/lib/plausible_web/templates/billing/upgrade_to_plan.html.eex
+++ b/lib/plausible_web/templates/billing/upgrade_to_plan.html.eex
@@ -10,12 +10,12 @@
       </div>
 
       <div class="w-full py-4 dark:text-gray-100">
-        <span>With this link you can upgrade to a plan with <b><%= PlausibleWeb.StatsView.large_number_format(@plan[:limit]) %> monthly pageviews</b></span>, billed on a <%= @plan[:cycle] %> basis.
+        <span>With this link you can upgrade to an enterprise plan with <b><%= PlausibleWeb.StatsView.large_number_format(@user.enterprise_plan.monthly_pageview_limit) %> monthly pageviews</b></span>, billed on a <%= @user.enterprise_plan.billing_interval %> basis.
       </div>
 
       <div class="mt-6 text-left">
         <span class="inline-flex w-full rounded-md shadow-sm">
-          <button type="button" data-theme="none" data-product="<%= @plan[:product_id] %>" data-email="<%= @conn.assigns[:current_user].email %>" data-disable-logout="true" data-passthrough="<%= @conn.assigns[:current_user].id %>" data-success="/billing/upgrade-success" class="items-center button paddle_button">
+          <button type="button" data-theme="none" data-product="<%= @user.enterprise_plan.paddle_plan_id %>" data-email="<%= @conn.assigns[:current_user].email %>" data-disable-logout="true" data-passthrough="<%= @conn.assigns[:current_user].id %>" data-success="/billing/upgrade-success" class="items-center button paddle_button">
             <svg fill="currentColor" viewBox="0 0 20 20" class="inline w-4 h-4 mr-2"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path></svg>
             Pay securely via Paddle
           </button>
diff --git a/lib/plausible_web/templates/email/enterprise_over_limit.html.eex b/lib/plausible_web/templates/email/enterprise_over_limit.html.eex
new file mode 100644
index 0000000000000000000000000000000000000000..6fea4741b28a6e79d8c7aeedfb6f3f96e7341543
--- /dev/null
+++ b/lib/plausible_web/templates/email/enterprise_over_limit.html.eex
@@ -0,0 +1,8 @@
+Automated notice about an account that has gone over their enteprise plan limit.
+
+Customer email: <% @user.email %>
+Last billing cycle: <%= date_format(@last_cycle.first) %> to <%= date_format(@last_cycle.last) %>
+Usage: <%= PlausibleWeb.StatsView.large_number_format(@usage) %> billable pageviews
+
+--<br />
+<%= plausible_url() %><br />
diff --git a/lib/workers/check_usage.ex b/lib/workers/check_usage.ex
index d6703c8115f0d16c632e658c11a6438e74865db1..a596b86dd321d9311d6ad32f2f81155ff3493a56 100644
--- a/lib/workers/check_usage.ex
+++ b/lib/workers/check_usage.ex
@@ -38,31 +38,47 @@ defmodule Plausible.Workers.CheckUsage do
         from u in Plausible.Auth.User,
           join: s in Plausible.Billing.Subscription,
           on: s.user_id == u.id,
+          left_join: ep in Plausible.Billing.EnterprisePlan,
+          on: ep.user_id == u.id,
           where: s.status == "active",
           where: not is_nil(s.last_bill_date),
           # Accounts for situations like last_bill_date==2021-01-31 AND today==2021-03-01. Since February never reaches the 31st day, the account is checked on 2021-03-01.
           where:
             least(day_of_month(s.last_bill_date), day_of_month(last_day_of_month(^yesterday))) ==
               day_of_month(^yesterday),
-          preload: [subscription: s]
+          preload: [subscription: s, enterprise_plan: ep]
       )
 
     for subscriber <- active_subscribers do
       allowance = Plausible.Billing.Plans.allowance(subscriber.subscription)
       {last_last_month, last_month} = billing_mod.last_two_billing_months_usage(subscriber)
+      is_over_limit = last_last_month > allowance && last_month > allowance
 
-      if last_last_month > allowance && last_month > allowance do
-        {_, last_cycle} = billing_mod.last_two_billing_cycles(subscriber)
-        suggested_plan = Plausible.Billing.Plans.suggested_plan(subscriber, last_month)
+      cond do
+        is_over_limit && subscriber.enterprise_plan ->
+          {_, last_cycle} = billing_mod.last_two_billing_cycles(subscriber)
 
-        template =
-          PlausibleWeb.Email.over_limit_email(subscriber, last_month, last_cycle, suggested_plan)
+          template =
+            PlausibleWeb.Email.enterprise_over_limit_email(subscriber, last_month, last_cycle)
 
-        try do
-          Plausible.Mailer.send_email(template)
-        rescue
-          _ -> nil
-        end
+          Plausible.Mailer.send_email_safe(template)
+
+        is_over_limit ->
+          {_, last_cycle} = billing_mod.last_two_billing_cycles(subscriber)
+          suggested_plan = Plausible.Billing.Plans.suggested_plan(subscriber, last_month)
+
+          template =
+            PlausibleWeb.Email.over_limit_email(
+              subscriber,
+              last_month,
+              last_cycle,
+              suggested_plan
+            )
+
+          Plausible.Mailer.send_email_safe(template)
+
+        true ->
+          nil
       end
     end
 
diff --git a/priv/repo/migrations/20211020093238_add_enterprise_plans.exs b/priv/repo/migrations/20211020093238_add_enterprise_plans.exs
new file mode 100644
index 0000000000000000000000000000000000000000..f6b9e23b401113f79f4a8b58502956c432988393
--- /dev/null
+++ b/priv/repo/migrations/20211020093238_add_enterprise_plans.exs
@@ -0,0 +1,19 @@
+defmodule Plausible.Repo.Migrations.AddEnterprisePlans do
+  use Ecto.Migration
+
+  def change do
+    create_query = "CREATE TYPE billing_interval AS ENUM ('monthly', 'yearly')"
+    drop_query = "DROP TYPE billing_interval"
+    execute(create_query, drop_query)
+
+    create table(:enterprise_plans) do
+      add :user_id, references(:users), null: false, unique: true
+      add :paddle_plan_id, :string, null: false
+      add :billing_interval, :billing_interval, null: false
+      add :monthly_pageview_limit, :integer, null: false
+      add :hourly_api_request_limit, :integer, null: false
+
+      timestamps()
+    end
+  end
+end
diff --git a/test/plausible_web/controllers/billing_controller_test.exs b/test/plausible_web/controllers/billing_controller_test.exs
index 61e02d81c6361e29fc0c56ce2c024b26d6e838df..8c0b408c03d78ff57ea3d99dacae32cd97e01689 100644
--- a/test/plausible_web/controllers/billing_controller_test.exs
+++ b/test/plausible_web/controllers/billing_controller_test.exs
@@ -2,6 +2,46 @@ defmodule PlausibleWeb.BillingControllerTest do
   use PlausibleWeb.ConnCase
   import Plausible.TestUtils
 
+  describe "GET /upgrade" do
+    setup [:create_user, :log_in]
+
+    test "shows upgrade page when user does not have a subcription already", %{conn: conn} do
+      conn = get(conn, "/billing/upgrade")
+
+      assert html_response(conn, 200) =~ "Upgrade your free trial"
+    end
+
+    test "redirects user to change plan if they already have a plan", %{conn: conn, user: user} do
+      insert(:subscription, user: user)
+      conn = get(conn, "/billing/upgrade")
+
+      assert redirected_to(conn) == "/billing/change-plan"
+    end
+
+    test "redirects user to enteprise plan page if they are configured with one", %{
+      conn: conn,
+      user: user
+    } do
+      plan = insert(:enterprise_plan, user: user)
+      conn = get(conn, "/billing/upgrade")
+
+      assert redirected_to(conn) == "/billing/upgrade/enterprise/#{plan.id}"
+    end
+  end
+
+  describe "GET /upgrade/enterprise/:plan_id" do
+    setup [:create_user, :log_in]
+
+    test "renders enteprise plan upgrade page", %{conn: conn, user: user} do
+      plan = insert(:enterprise_plan, user: user)
+
+      conn = get(conn, "/billing/upgrade/enterprise/#{plan.id}")
+
+      assert html_response(conn, 200) =~ "Upgrade your free trial"
+      assert html_response(conn, 200) =~ "enterprise plan"
+    end
+  end
+
   describe "GET /change-plan" do
     setup [:create_user, :log_in]
 
@@ -17,6 +57,37 @@ defmodule PlausibleWeb.BillingControllerTest do
 
       assert redirected_to(conn) == "/billing/upgrade"
     end
+
+    test "redirects to enterprise change plan page if user has enterprise plan and existing subscription",
+         %{conn: conn, user: user} do
+      insert(:subscription, user: user)
+      plan = insert(:enterprise_plan, user: user)
+      conn = get(conn, "/billing/change-plan")
+
+      assert redirected_to(conn) == "/billing/change-plan/enterprise/#{plan.id}"
+    end
+  end
+
+  describe "GET /change-plan/enterprise/:plan_id" do
+    setup [:create_user, :log_in]
+
+    test "shows change plan page if user has subsription and enterprise plan", %{
+      conn: conn,
+      user: user
+    } do
+      insert(:subscription, user: user)
+      plan = insert(:enterprise_plan, user: user)
+      conn = get(conn, "/billing/change-plan/enterprise/#{plan.id}")
+
+      assert html_response(conn, 200) =~ "Change subscription plan"
+    end
+
+    test "renders 404 is user does not have enterprise plan", %{conn: conn, user: user} do
+      insert(:subscription, user: user)
+      conn = get(conn, "/billing/change-plan/enterprise/123")
+
+      assert conn.status == 404
+    end
   end
 
   describe "POST /change-plan" do
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 3e6e11dae27410c9584fc8b22905b0bdbc300a95..02727def53c14ca89d67580101832f7b3f989864 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -116,6 +116,15 @@ defmodule Plausible.Factory do
     }
   end
 
+  def enterprise_plan_factory do
+    %Plausible.Billing.EnterprisePlan{
+      paddle_plan_id: sequence(:paddle_plan_id, &"plan-#{&1}"),
+      billing_interval: :monthly,
+      monthly_pageview_limit: 1_000_000,
+      hourly_api_request_limit: 3000
+    }
+  end
+
   def google_auth_factory do
     %Plausible.Site.GoogleAuth{
       email: sequence(:google_auth_email, &"email-#{&1}@email.com"),
diff --git a/test/workers/check_usage_test.exs b/test/workers/check_usage_test.exs
index 4df0daac05fc9d4a5b9622241cecdca6a9150ecc..7441f64e8a77a3cef3fbd5f1e9e1cd3bef8ded4d 100644
--- a/test/workers/check_usage_test.exs
+++ b/test/workers/check_usage_test.exs
@@ -64,7 +64,34 @@ defmodule Plausible.Workers.CheckUsageTest do
 
     assert_email_delivered_with(
       to: [user],
-      subject: "You have outgrown your Plausible subscription tier "
+      subject: "You have outgrown your Plausible subscription tier"
+    )
+  end
+
+  test "checks usage for enterprise customer, sends usage information to enterprise@plausible.io",
+       %{
+         user: user
+       } do
+    billing_stub =
+      Plausible.Billing
+      |> stub(:last_two_billing_months_usage, fn _user -> {1_100_000, 1_100_000} end)
+      |> stub(:last_two_billing_cycles, fn _user ->
+        {Date.range(Timex.today(), Timex.today()), Date.range(Timex.today(), Timex.today())}
+      end)
+
+    enterprise_plan = insert(:enterprise_plan, user: user, monthly_pageview_limit: 1_000_000)
+
+    insert(:subscription,
+      user: user,
+      paddle_plan_id: enterprise_plan.paddle_plan_id,
+      last_bill_date: Timex.shift(Timex.today(), days: -1)
+    )
+
+    CheckUsage.perform(nil, billing_stub)
+
+    assert_email_delivered_with(
+      to: [{nil, "enterprise@plausible.io"}],
+      subject: "#{user.email} has outgrown their enterprise plan"
     )
   end
 
@@ -89,7 +116,7 @@ defmodule Plausible.Workers.CheckUsageTest do
 
       assert_email_delivered_with(
         to: [user],
-        subject: "You have outgrown your Plausible subscription tier "
+        subject: "You have outgrown your Plausible subscription tier"
       )
     end
 
@@ -135,7 +162,7 @@ defmodule Plausible.Workers.CheckUsageTest do
 
       assert_email_delivered_with(
         to: [user],
-        subject: "You have outgrown your Plausible subscription tier "
+        subject: "You have outgrown your Plausible subscription tier"
       )
     end
   end