Calculate email list growth rate and gross signup rate
Email list growth can look healthy while subscriber losses quietly offset a large share of acquisition.
Run — free
This calculator separates those two views. Enter the subscriber count at the beginning and end of a period, plus the number of unsubscribes recorded during that period. It calculates the net change, reconstructs gross signups, and expresses both net growth and gross acquisition as percentages of the starting list, giving you comparable metrics for weekly, monthly, or campaign reporting.
Understand net growth and gross acquisition
Net growth describes how much larger or smaller the list became after subscriber additions and unsubscribes were combined. It is calculated by subtracting the starting subscriber count from the ending count, dividing that change by the starting count, and multiplying by 100. Gross signup rate answers a different question: how many people joined before losses were deducted, relative to the original list size? Because the input provides unsubscribes rather than signups, the calculator reconstructs gross signups by adding unsubscribes back to the net subscriber change. For example, if a list starts with 10,000 subscribers, ends with 10,800, and records 200 unsubscribes, its net change is 800 while its inferred gross signups equal 1,000. Net growth is therefore 8%, but gross signup rate is 10%. Seeing both figures prevents acquisition performance from being hidden by churn and prevents a strong acquisition month from being mistaken for equally strong retained growth. Use the rates together rather than treating either one as the complete story.
Prepare a consistent reporting period
Choose one clearly bounded period before entering counts, such as a calendar month, a campaign window, or a four-week reporting cycle. The starting count should be the active subscriber total at the opening boundary, while the ending count should be the equivalent active total at the closing boundary. Unsubscribes must cover that same interval and should follow the same definition used by your email platform. Do not mix a month-end list total with unsubscribes from only one campaign, because the inferred signup figure would combine incompatible scopes. Also consider whether your platform removes bounced, suppressed, or manually deleted contacts from the active count. If those removals reduce the ending total but are not included in the unsubscribe input, the reconstructed gross signups may not match the platform's signup report. The calculator intentionally uses the three supplied counts and does not guess at undocumented adjustments. Record the source and timestamp of each input so future reports can be reproduced, and apply the same inclusion rules from one period to the next.
Interpret the rates without losing context
Compare periods of similar length and use the starting list as the common denominator. A 5% monthly gross signup rate is not directly comparable to a 5% weekly rate, and adding weekly percentages does not reliably produce a monthly result because the subscriber base changes over time. Net growth can be negative even when signups occurred, which means losses exceeded acquisition during the period. Gross signup rate can be positive while net growth is flat, revealing that the team acquired subscribers but replaced only those who left. Investigate that pattern alongside unsubscribe reasons, acquisition sources, engagement, and deliverability rather than judging the signup program alone. The calculator rounds displayed rates to six decimal places for stable, repeatable output, but the underlying formulas use the supplied integer counts. If the implied gross signup count would be negative, the inputs are internally inconsistent and are rejected. A starting count of zero is also rejected because neither percentage has a defined denominator; use absolute signup counts until the list has a nonzero starting base.
What you can do with it
Monthly newsletter reporting
Separate acquisition activity from retained list growth in a recurring marketing dashboard.
Campaign performance review
Compare gross signups generated during a campaign with the net change after unsubscribes.
Churn diagnosis
Identify periods where healthy signup volume produced weak or negative net list growth.
FAQ
How is net email list growth rate calculated?
Subtract starting subscribers from ending subscribers, divide by starting subscribers, and multiply by 100.
How are gross signups calculated without a signup input?
Gross signups are inferred as ending subscribers minus starting subscribers plus unsubscribes.
Why does the starting subscriber count have to be greater than zero?
Both rates divide by the starting count, so a zero starting count makes the percentages undefined.
Can net growth rate be negative?
Yes. A negative rate means the ending list is smaller than the starting list.
What does an inconsistent-count error mean?
It means the supplied totals imply fewer than zero gross signups, so at least one count or reporting boundary is incorrect.
What does an API request cost?
Each API request costs $0.002. The calculator can also run in the browser.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/biz/email-list-growth-rate \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"starting_subscribers":10000,"ending_subscribers":10800,"unsubscribes":200}'const res = await fetch("https://api.kit.forhosting.com/biz/email-list-growth-rate", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"starting_subscribers": 10000,
"ending_subscribers": 10800,
"unsubscribes": 200
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/biz/email-list-growth-rate",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"starting_subscribers": 10000,
"ending_subscribers": 10800,
"unsubscribes": 200
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/biz/email-list-growth-rate", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"starting_subscribers":10000,"ending_subscribers":10800,"unsubscribes":200}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"starting_subscribers":10000,"ending_subscribers":10800,"unsubscribes":200}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/biz/email-list-growth-rate", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"starting_subscribers": 10000,
"ending_subscribers": 10800,
"unsubscribes": 200
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "biz.email_list_growth_rate",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |