Skip to content

Create an email account on web hosting

4 steps · Checked against the API code 3 November 2026

A guide does one task from start to finish. Every endpoint, with all its fields and cases, is in the API reference.

A mailbox on Hostup web hosting is an address such as [email protected] with its own password and storage. You create it on the web hosting account that holds the domain. The mailbox exists as soon as the call returns. There is no job to wait for.

  • An API key with the scopes write:hosting and console:services. write:hosting also covers read:hosting. Keys and scopes
  • The requests read the key from $HOSTUP_API_KEY, as set in the Quickstart.
  • The domain for the new address, added to a web hosting account. Step 1 finds the account that holds it.

1. Find the web hosting account that holds the domain

Section titled “1. Find the web hosting account that holds the domain”

List web hosting accounts with include=domains returns your web hosting accounts and the domains on each.

GET /api/v2/shared-hosting
curl "https://cloud.hostup.se/api/v2/shared-hosting?include=domains" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"data": [
{
"id": "acct_01hxa3b4c5d6e7f8g9h0j1k2m3",
"primaryDomain": "example.se",
"domains": ["example.se", "example.com"],
"serviceStatus": "active",
"controlPanel": { "type": "cpanel" },
"…": "…"
}
],
"hasMore": false,
"nextCursor": null
}

Find the account whose domains has the domain for the new address. Pick an account with serviceStatus active and controlPanel.type cpanel. Keep its id:

export ACCOUNT_ID="acct_…" # data[].id of that account

The list comes 20 per page. When hasMore is true, send nextCursor as cursor for the next page. Pagination

domains leaves subdomains out. Find the account by the parent domain, such as example.se for shop.example.se. List hosting domains then shows every domain on that account, subdomains included. A mailbox can use any of them.

GET /api/v2/shared-hosting/{accountId}/domains
curl "https://cloud.hostup.se/api/v2/shared-hosting/$ACCOUNT_ID/domains" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"domains": [
{ "domain": "example.se", "kind": "main" },
{ "domain": "shop.example.se", "kind": "sub" }
],
"mainDomain": "example.se",
"…": "…"
}

A domain that is not on the account returns 403 domain_not_owned in every mailbox call. Add a domain to web hosting adds it to the account.

2. Check that the plan has room for another mailbox

Section titled “2. Check that the plan has room for another mailbox”

List email accounts returns the mailboxes that exist and whether you can create one more (actions.canCreate).

GET /api/v2/shared-hosting/{accountId}/email-accounts
curl "https://cloud.hostup.se/api/v2/shared-hosting/$ACCOUNT_ID/email-accounts" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"emailAccounts": [
{
"id": "mail_01hxb4c5d6e7f8g9h0j1k2m3n4",
"email": "[email protected]",
"domain": "example.se",
"quota": { "usedGb": 0.125, "limitGb": 32, "usagePercent": 0.39, "exceeded": false }
}
],
"quota": { "used": 1, "limit": 10, "remaining": 9 },
"actions": {
"canCreate": { "allowed": true, "reason": null }
}
}

When canCreate.allowed is false, reason says why, for example “Email account limit reached (10). Upgrade your plan to add more.” quota.limit is the number of mailboxes the plan allows. null means no limit.

Check canCreate.allowed before you create. The create call does not check the limit. Past it, the create fails with 502 upstream_failed. A retry fails the same way.

Send the part before @ as localPart and the domain as domain.

POST /api/v2/shared-hosting/{accountId}/email-accounts
curl -X POST "https://cloud.hostup.se/api/v2/shared-hosting/$ACCOUNT_ID/email-accounts" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"localPart": "info", "domain": "example.se", "password": "Tall-Harbor-58-Lantern", "quotaMb": 5120}'
Response 201
{
"id": "mail_01hxa3b4c5d6e7f8g9h0j1k2m3",
"email": "[email protected]",
"domain": "example.se",
"quota": { "usedGb": 0, "limitGb": 5, "usagePercent": 0, "exceeded": false }
}
  • localPart: the letters a–z and A–Z, digits and the characters . _ % + -, at most 64 characters. A full address returns 400 invalid_request. So do letters such as å, ä or ö.
  • domain: one of the account’s domains from step 1. A domain with letters such as å, ä or ö works in its readable spelling and in its xn-- spelling. The response uses the readable one.
  • password: 8 to 128 characters with at least one uppercase letter, one lowercase letter and one digit. It must also be hard to guess and not close to the address or the domain. Otherwise the response is password_too_weak. Generate a new password for every mailbox.
  • quotaMb: the mailbox’s storage in megabytes, from 0 to 1048576. Leave it out for 32768 (32 GB). 0 means no limit. The response then shows limitGb as null.

The response never repeats the password. Keep id. Every call on the mailbox accepts it in place of the address:

export MAILBOX_ID="mail_…" # id above

Get mail app settings for a domain returns the servers and ports for the mailbox’s domain. The domain parameter is required.

GET /api/v2/shared-hosting/{accountId}/mail-settings
curl "https://cloud.hostup.se/api/v2/shared-hosting/$ACCOUNT_ID/mail-settings?domain=example.se" \
-H "Authorization: Bearer $HOSTUP_API_KEY"
Response 200
{
"domain": "example.se",
"incoming": {
"imap": { "hostname": "server.example.net", "port": 993, "encryption": "ssl_tls" },
"pop3": { "hostname": "server.example.net", "port": 995, "encryption": "ssl_tls" }
},
"outgoing": {
"smtp": [
{ "hostname": "server.example.net", "port": 465, "encryption": "ssl_tls" },
{ "hostname": "server.example.net", "port": 587, "encryption": "starttls" }
]
},
"usernameFormat": "full_email_address",
"verifiedDomainHostname": null,
"alternativeHostname": "mail.example.se",
"webmailUrl": "https://server.example.net:2096",
"…": "…"
}
  • The username is the full address, [email protected]. The password is the mailbox password from step 3.
  • The server is verifiedDomainHostname when it is set, such as mail.example.se. When it is null, use hostname. Never use alternativeHostname in its place: that name is not checked.
  • webmailUrl opens webmail in a browser. Create a webmail login link returns a loginUrl that opens the inbox once, without the password.

Download an Apple Mail setup profile returns a profile that sets up the mailbox in the Mail app. The response is the file itself, not JSON.

GET /api/v2/shared-hosting/{accountId}/email-accounts/{address}/mobileconfig
curl -f -o [email protected] \
"https://cloud.hostup.se/api/v2/shared-hosting/$ACCOUNT_ID/email-accounts/$MAILBOX_ID/mobileconfig" \
-H "Authorization: Bearer $HOSTUP_API_KEY"

The profile never holds the password. The device asks for it when the profile is installed.

To put the profile on a phone that has no API key, create an Apple Mail setup link instead. Show it as a QR code and scan it with the phone.

POST /api/v2/shared-hosting/{accountId}/email-accounts/{address}/actions/mail-profile-link
curl -X POST "https://cloud.hostup.se/api/v2/shared-hosting/$ACCOUNT_ID/email-accounts/$MAILBOX_ID/actions/mail-profile-link" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json"
Response 200
{
"url": "https://cloud.hostup.se/api/v2/mail-profiles/v1.…",
"expiresAt": "2026-11-02T10:45:00.000Z"
}

The link opens without a key or a login for 30 minutes. After that it returns 410 link_expired. Create a new one.

The calls below use $MAILBOX_ID from step 3. The address works in its place too, such as [email protected]. Write a % in the address as %25.

The new password follows the same rules as in step 3. Mail apps that still use the old password lose access until they get the new one.

PUT /api/v2/shared-hosting/{accountId}/email-accounts/{address}/password
curl -X PUT "https://cloud.hostup.se/api/v2/shared-hosting/$ACCOUNT_ID/email-accounts/$MAILBOX_ID/password" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"newPassword": "Quiet-Meadow-73-Copper"}'
Response 200
{
"email": "[email protected]"
}

quotaMb is required here. It follows the same rules as in step 3.

PUT /api/v2/shared-hosting/{accountId}/email-accounts/{address}/quota
curl -X PUT "https://cloud.hostup.se/api/v2/shared-hosting/$ACCOUNT_ID/email-accounts/$MAILBOX_ID/quota" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"quotaMb": 10240}'
Response 200
{
"email": "[email protected]",
"quota": { "usedGb": 0.2, "limitGb": 10, "usagePercent": 2, "exceeded": false }
}

exceeded is true when the mailbox already holds more than the new limit.

Create an email forwarder forwards mail for an address to up to 20 other addresses. The address does not need a mailbox of its own.

POST /api/v2/shared-hosting/{accountId}/email-forwarders
curl -X POST "https://cloud.hostup.se/api/v2/shared-hosting/$ACCOUNT_ID/email-forwarders" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"localPart": "info", "domain": "example.se", "destinations": ["[email protected]"]}'
Response 201
{
"id": "efwd_01hxa3b4c5d6e7f8g9h0j1k2m3",
"email": "[email protected]",
"domain": "example.se",
"localPart": "info",
"action": "forward",
"destinations": ["[email protected]"]
}

For an address that should drop all mail, such as [email protected], send "action": "discard" without destinations.

Forwarders have their own plan limit. List email forwarders shows it in actions.canCreate. Check it before you create. The create call does not check it either.

Delete an email forwarder removes the destinations you name in destinations. Always send them. Without destinations the call removes every destination of the address.

DELETE /api/v2/shared-hosting/{accountId}/email-forwarders/{address}
curl -X DELETE "https://cloud.hostup.se/api/v2/shared-hosting/$ACCOUNT_ID/email-forwarders/[email protected]" \
-H "Authorization: Bearer $HOSTUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"destinations": ["[email protected]"]}'

The response is 204 with no body. To remove a discard address, send {"action": "discard"} as the body.

DELETE /api/v2/shared-hosting/{accountId}/email-accounts/{address}
curl -X DELETE "https://cloud.hostup.se/api/v2/shared-hosting/$ACCOUNT_ID/email-accounts/$MAILBOX_ID" \
-H "Authorization: Bearer $HOSTUP_API_KEY"

The response is 204 with no body.

  • The mailbox works as soon as the create call returns 201. Nothing is billed and no e-mail goes out. It counts against the plan’s mailbox limit from step 2.
  • Mail from outside reaches the mailbox only when the domain’s MX records point at the web hosting. When Get email routing for a domain returns mode remote for the domain, the hosting does not accept mail for it.
  • 400 password_too_weak: the password breaks a rule. detail says which. When it is too easy to guess, the response also has score and suggestions. The score runs from 0 to 4 and must be at least 3. Send a longer generated password.
  • 409 email_in_use: the mailbox exists already. Change its password or storage instead of creating it again.
  • 502 upstream_failed: the hosting server refused the call or did not answer. Creating a mailbox past the plan’s limit gives this code. So does a change to a mailbox that does not exist. Check canCreate and the mailbox list from step 2 before you try again.
  • 403 domain_not_owned: the domain is not on this web hosting account. Use a domain from step 1. Check that the account ID is the right one.
  • 403 forbidden: with requiredScopes, the key lacks a scope (Keys and scopes). Without it, the account is not active. Check serviceStatus from step 1.
  • 409 account_not_eligible_cpanel: the account is on Hostup’s previous hosting platform. Contact support. Hostup moves the account to cPanel at no extra charge. No new hosting plan is needed.

Next: Connect a domain’s email to Hostup web hosting: point the domain’s mail at the mailbox.