Create a Sign-In Link
Make a one-time link that signs someone in to the SpamFoo Dashboard with the role you choose. A mail server or control panel can use it to send its users to the dashboard without sharing the proxy secret.
POST
/generate-login-linkWhen to Use It
- Add a "SpamFoo Dashboard" link to your own webmail or control panel, and make a fresh sign-in link each time a user clicks it.
- Give a domain administrator access to the domains they manage, and nothing else.
To make a link by hand on the server, use spamfoo-client login-link instead. For what each role sees, see Manage Sign-In and Roles.
Request
Send the current proxy secret in X-SpamFoo-Proxy-Auth, even from loopback. Send a JSON body with Content-Type: application/json.
{
"role": "DomainAdmin",
"domains": ["example.com", "example.org"],
"email": "[email protected]",
"expiresInMinutes": 15,
"returnPath": "/admin/"
}
| Field | Required | Type | Description |
|---|---|---|---|
role |
Yes | string | SystemAdmin, DomainAdmin, or User. Letter case doesn't matter. |
domain or domains |
For DomainAdmin |
string, or array of strings | The domain, or up to 50 domains, a Domain Admin can manage. Send one of the two fields, not both. Don't send either for the other roles. |
email |
For User |
email address | The mailbox a User sees. For a Domain Admin, also shows that mailbox's own pages. Optional for SystemAdmin and DomainAdmin. |
expiresInMinutes |
No | integer | How long the link can be used, from 1 to 1440. The default is 15. The sign-in it creates lasts 24 hours either way. |
returnPath |
No | string | A dashboard page to open after sign-in. It must be /admin or start with /admin/, and be at most 1024 characters. |
Domains and email addresses are stored in lowercase, and international domain names in their Unicode form, so Example.COM and example.com are the same domain.
Response
{
"url": "http://spamfoo.example.com:16253/admin/login?t=...",
"expiresAt": "2026-10-13T14:15:00+00:00"
}
urlstarts withSPAMFOO_PUBLIC_URLin standalone mode when it's set. Otherwise it uses the address your request was sent to, so call SpamFoo Client at the address the user's browser will use.- Opening the link shows a Sign in to SpamFoo page. The link is used up only when the person clicks Continue, so a mail scanner that opens links in advance doesn't use it.
- Each link works once. Changing the proxy secret stops every unused link.
Warning Treat a sign-in link like a password until it's used or expires. Send it only to the person it's for, over HTTPS where you can.
Errors
| Status | Body | Cause |
|---|---|---|
400 |
{"error": "..."} |
A field is missing or invalid, such as role must be SystemAdmin, DomainAdmin or User. or expiresInMinutes must be between 1 and 1440. The message names the field. |
401 |
Empty | The proxy secret was missing or wrong. |
429 |
{"error": "...", "code": "auth_blocked"} |
Standalone mode only. Too many wrong secrets came from this address. See Repeated Wrong Secrets. |
Example
curl -s -X POST http://localhost:16253/generate-login-link \
-H "X-SpamFoo-Proxy-Auth: $SPAMFOO_PROXY_SECRET" \
-H "Content-Type: application/json" \
-d '{"role":"User","email":"[email protected]"}'
using System.Net.Http.Json;
var secret = Environment.GetEnvironmentVariable("SPAMFOO_PROXY_SECRET")
?? throw new InvalidOperationException("Set SPAMFOO_PROXY_SECRET.");
using var http = new HttpClient { BaseAddress = new Uri("http://localhost:16253") };
http.DefaultRequestHeaders.Add("X-SpamFoo-Proxy-Auth", secret);
using var response = await http.PostAsJsonAsync("/generate-login-link",
new { role = "User", email = "[email protected]" });
response.EnsureSuccessStatusCode();
var link = await response.Content.ReadFromJsonAsync<LoginLink>();
Console.WriteLine(link!.Url);
record LoginLink(string Url, DateTimeOffset ExpiresAt);
import os
import requests
response = requests.post(
"http://localhost:16253/generate-login-link",
headers={"X-SpamFoo-Proxy-Auth": os.environ["SPAMFOO_PROXY_SECRET"]},
json={"role": "User", "email": "[email protected]"},
timeout=10,
)
response.raise_for_status()
print(response.json()["url"])