Technical Support
Step by step — from registration to your first email
🚀 Getting Started
1. How to purchase a license
Go to Account → 📜 Licenses.
Choose the number of licenses and billing period (monthly or yearly).
Click Buy Licenses and complete the payment.
Licenses are activated immediately after successful payment.
2. How to add your domain
Go to My Account → 🌐 Domains.
Click + Add Domain and enter your domain name.
Press Verify. Verification happens automatically within a few minutes.
The system will display DNS records — add them to your domain's DNS manager.
2a. Or buy a domain in the 🌐 + Buy domain section — find your domain with the search tool and complete the purchase.
3. How to create an email account
Go to My Account → 📧 Email Accounts.
Click + Add Email Account.
Choose a domain, enter a username and a strong password (min. 12 characters).
The email account is ready to use immediately after creation.
📬 Email Client Setup
Mozilla Thunderbird
Open Thunderbird → Account → Add email account.
Enter your name, email and password. Thunderbird usually detects settings automatically.
Advanced guide
The section below is for web developers who want to add a "Sign in with ProMail" button to their website.
✅ If you use ProMail for email — everything you need is described above. You can skip this section.
🛠️ If you are building a website — below you will find the configuration, code examples and common mistakes.
🔐 Sign in with ProMail on your website
Let people sign in to your website with their ProMail account instead of creating another password. ProMail handles the passwords, passkeys and fingerprint prompts; your site receives a verified identity.
ProMail is a standard OpenID Connect provider. If your framework has an OIDC library, integration takes about 10 minutes — you will not need to write any cryptography.
⚙️ How it works
Your site sends the user to ProMail
They click your button and are redirected to promail.lv.
ProMail signs them in
Passkey, fingerprint or password. Your site never sees any of it.
The user returns with a code
Your server exchanges that code for an identity token, behind the scenes.
🔑 How to get your credentials
Go to Account → 🔑 Applications and click
➕ Add application.
Enter an application name and a redirect URI.
Each application uses one license slot. The same license can serve a domain and an email account at the same time — they do not take slots from each other.
📋 Configuration
💡 Most libraries need only the Discovery URL — they read everything else from it automatically, including the signing keys.
⚡ Quick start with a library
Recommended. Point your OIDC library at the Discovery URL.
WordPress
Install the plugin OpenID Connect Generic Client, then set:
Client ID : your client id Client Secret : your client secret OpenID Scope : openid email profile Login Endpoint : https://promail.lv/oidc/authorize Userinfo : https://promail.lv/oidc/userinfo Token Validation: https://promail.lv/oidc/token Identity Key : sub
Node.js
Package openid-client:
const promail = await Issuer.discover(
'https://promail.lv'
);
const client = new promail.Client({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
redirect_uris: [REDIRECT_URI],
response_types: ['code'],
});
Laravel / PHP
Package jumbojett/openid-connect-php:
$oidc = new OpenIDConnectClient(
'https://promail.lv',
CLIENT_ID,
CLIENT_SECRET
);
$oidc->addScope(['openid','email','profile']);
$oidc->setCodeChallengeMethod('S256');
$oidc->authenticate();
$sub = $oidc->requestUserInfo('sub');
Python
Library Authlib:
oauth.register(
name='promail',
server_metadata_url=
'https://promail.lv/.well-known/'
'openid-configuration',
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
client_kwargs={
'scope': 'openid email profile',
'code_challenge_method': 'S256',
},
)
🛠️ Plain PHP, no library
A complete working example. Two files: one starts the flow, one handles the return.
1. promail-login.php — the button links here
<?php
session_start();
define('ISSUER', 'https://promail.lv');
define('CLIENT_ID', 'your_client_id');
define('CLIENT_SECRET', 'your_client_secret');
define('REDIRECT_URI', 'https://yoursite.lv/promail-callback');
function b64url($b) {
return rtrim(strtr(base64_encode($b), '+/', '-_'), '=');
}
// PKCE. The verifier stays on your server; only its hash goes in the URL,
// so a stolen code cannot be redeemed without it.
$verifier = b64url(random_bytes(32));
$_SESSION['pm_verifier'] = $verifier;
$_SESSION['pm_state'] = $state = b64url(random_bytes(16));
$_SESSION['pm_nonce'] = $nonce = b64url(random_bytes(16));
header('Location: ' . ISSUER . '/oidc/authorize?' . http_build_query([
'client_id' => CLIENT_ID,
'redirect_uri' => REDIRECT_URI,
'response_type' => 'code',
'scope' => 'openid email profile',
'state' => $state,
'nonce' => $nonce,
'code_challenge' => b64url(hash('sha256', $verifier, true)),
'code_challenge_method' => 'S256',
]));
2. promail-callback.php — the redirect URI points here
<?php
session_start();
// ... the same define() lines as above ...
if (isset($_GET['error'])) {
exit('Sign-in failed: ' . htmlspecialchars($_GET['error']));
}
// state must match, or this is a forged request from another site
if (!hash_equals($_SESSION['pm_state'] ?? '', $_GET['state'] ?? '')) {
exit('Security check failed');
}
// Exchange the code. Server to server over TLS - the browser is not
// involved, which is why it is safe to send the client secret here.
$ch = curl_init(ISSUER . '/oidc/token');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => http_build_query([
'grant_type' => 'authorization_code',
'code' => $_GET['code'],
'redirect_uri' => REDIRECT_URI,
'code_verifier' => $_SESSION['pm_verifier'],
'client_id' => CLIENT_ID,
'client_secret' => CLIENT_SECRET,
]),
]);
$tokens = json_decode(curl_exec($ch), true);
curl_close($ch);
if (empty($tokens['id_token'])) { exit('Token exchange failed'); }
$claims = json_decode(base64_decode(strtr(
explode('.', $tokens['id_token'])[1], '-_', '+/')), true);
if ($claims['iss'] !== ISSUER) { exit('Wrong issuer'); }
if ($claims['aud'] !== CLIENT_ID) { exit('Wrong audience'); }
if ($claims['exp'] < time()) { exit('Token expired'); }
if ($claims['nonce'] !== $_SESSION['pm_nonce']) { exit('Nonce mismatch'); }
unset($_SESSION['pm_verifier'], $_SESSION['pm_state'], $_SESSION['pm_nonce']);
// $claims['sub'] - permanent user id. STORE THIS.
// $claims['email'] - may change. Do not use as the key.
// $claims['name']
$user = findUserByPromailSub($claims['sub']);
if (!$user) {
// First time. Link to an existing account, or create a new one.
}
⚠️ Common mistakes
Using the email as the account key
Use sub. It never changes. Emails do, and a reused address would hand the account to whoever gets it next.
Redirect URI does not match exactly
ProMail compares it byte for byte. https://site.lv/cb and https://site.lv/cb/ are two different addresses. This is the most common mistake.
Trusting the token's own alg field
Always require RS256 from your own configuration. A token claiming alg: none must be rejected.
Skipping the state check
Without it, an attacker can complete a login flow in the victim's browser and connect their own ProMail account to the victim's session.
Putting the Client Secret in JavaScript
It belongs on your server only. Anything in browser code is public.
Leaving out PKCE
ProMail requires it. Requests without code_challenge are refused.
🎨 The button
Use consistent wording and appearance so people recognise the button across sites.
<a href="/promail-login" class="promail-btn">
<img src="https://promail.lv/img/promail-logo.webp" alt="" width="20" height="20">
<span>Sign in with ProMail</span>
</a>
<style>
.promail-btn{display:inline-flex;align-items:center;justify-content:center;
gap:9px;padding:11px 18px;border:1px solid #d1d5db;border-radius:8px;
background:#fff;color:#111827;font:600 14px system-ui,sans-serif;
text-decoration:none}
.promail-btn:hover{background:#f9fafb;border-color:#9ca3af}
</style>
🧪 Testing
There is no separate sandbox. Register a second application with a http://localhost redirect URI — localhost is the only address allowed without https — and test your integration before going live.