Skip to content

Send test email

POST
/api/v1/admin/settings/email/send-test
Code sample: Shell / cURL
curl --request POST \
--url http://localhost:9090/api/v1/admin/settings/email/send-test \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "to": "[email protected]" }'

Send a short message to to through the stored SMTP settings, the stored password included, to check what a save’s connection does not: the encryption, the certificate, the credentials and the sender. Answers 200 once the SMTP server has accepted the message.

The send waits up to 10 seconds to connect, including the TLS handshake for SSL/TLS, and up to 30 seconds for the rest of the conversation.

Media typeapplication/json
object
to
required
string format: email

Example generated

Test email sent

Media typeapplication/json
object
success
required
boolean

Example generated

{
"success": true
}

Error_code is SMTP_NOT_ENABLED while SMTP is disabled, INVALID_REQUEST_BODY for a body that will not parse, VALIDATION_ERROR when to is missing, validator.email.invalid_format when it is not an email address, and SEND_FAILED when the send fails.

A SEND_FAILED answers one of these fixed messages, chosen by what failed:

  • “Unable to send the test email: host name not found.” Check the host name, as for a save.
  • “Unable to send the test email: connection timed out.” Check the port and any firewall, as for a save. A server that accepted the connection and then went quiet also ends here, which is what STARTTLS or None against an SSL/TLS port such as 465 looks like.
  • “Unable to send the test email: connection refused.” Check the port.
  • “The SMTP server did not offer STARTTLS; set the encryption to None only if the server has no TLS.” The encryption is STARTTLS and the server does not offer it. Goiabada does not continue unencrypted.
  • “The SMTP server would receive the password unencrypted; set the encryption to STARTTLS or SSL/TLS.” The encryption is None, a username is set, and the server would be sent the password in the clear by PLAIN or LOGIN. Only a server on localhost, 127.0.0.1 or ::1 is exempt.
  • “SMTP credentials are configured but the server offers no authentication.” A username is set and the server offers no AUTH: the wrong host or port, or a server that offers authentication only after STARTTLS, reached with the encryption set to None.
  • “The SMTP server offers none of PLAIN, LOGIN or CRAM-MD5.” The server offers only mechanisms Goiabada does not implement, such as XOAUTH2: use a password the server accepts through one of these three, often called an app password or an SMTP key.
  • “The TLS connection failed; check the encryption setting and the server’s certificate.” SSL/TLS against a port that expects STARTTLS, such as 587; a certificate the auth server does not trust, that has expired, or that names a host other than smtpHost; or a server that refused the STARTTLS command. Certificates are always verified.
  • “The SMTP server rejected the username or password.” Check the username and password.
  • “The SMTP server refused the message.” The server refused the sender, the recipient or the message itself, most often because the account may not send from smtpFromEmail.
  • “Unable to send the test email.” Any other failure: a connection that dropped partway through the conversation, a network the auth server has no route to, or a stored password the auth server cannot decrypt, such as one encrypted under a GOIABADA_AES_ENCRYPTION_KEY it no longer has.

Nothing from the error itself is answered, no address, operating-system error or server reply; the full error goes to the auth server’s log, at Warn, as “unable to send the test email” under the request’s request_id.

Media typeapplication/json

Admin/account API error envelope. Routing by HTTP status code: 4xx is a client-correctable failure (validation, not found, permission denied, rate limited); 5xx is a server-side problem. The body provides a stable error_code and an error_description for people to read. Protocol endpoints (/auth/token, /auth/authorize, /connect/register, /userinfo) use their RFC-defined error envelopes instead of this one.

object
error_code

Stable identifier for the failure. UPPER_SNAKE for legacy codes (e.g. VALIDATION_ERROR), dotted lowercase for catalog-keyed localized codes (e.g. validator.email.required).

string
error_description
required

A sentence for people to read: in the request’s language when error_code is a catalog key, in English otherwise. Route on error_code, not on this text.

string

Example generated

{
"error_code": "example",
"error_description": "example"
}

Missing or invalid bearer token

Media typeapplication/json

Admin/account API error envelope. Routing by HTTP status code: 4xx is a client-correctable failure (validation, not found, permission denied, rate limited); 5xx is a server-side problem. The body provides a stable error_code and an error_description for people to read. Protocol endpoints (/auth/token, /auth/authorize, /connect/register, /userinfo) use their RFC-defined error envelopes instead of this one.

object
error_code

Stable identifier for the failure. UPPER_SNAKE for legacy codes (e.g. VALIDATION_ERROR), dotted lowercase for catalog-keyed localized codes (e.g. validator.email.required).

string
error_description
required

A sentence for people to read: in the request’s language when error_code is a catalog key, in English otherwise. Route on error_code, not on this text.

string

Example generated

{
"error_code": "example",
"error_description": "example"
}
WWW-Authenticate
string

Bearer challenge on token refusals. An operation may also return a handler-level 401 without this header.

The token is valid but does not authorise this request. Four checks answer here, and a caller distinguishes them by error_code.

INSUFFICIENT_SCOPE comes from the route’s authorisation middleware, before the handler runs: an Admin operation needs one of the scopes its route names, and an Account operation needs authserver:manage-account.

USER_CONTEXT_REQUIRED comes from the same middleware on the Account operations, which also require the token to have been issued for a user: one obtained through the client credentials grant carries no auth_time claim and is refused here.

FORBIDDEN comes from the handler, on the account endpoints that resolve a resource by id and then check it belongs to the caller, so a valid token for the wrong user reaches this rather than a 404.

MANAGE_SCOPE_REQUIRED comes from the handler, on the Admin operations the administrative policy guards. Only authserver:manage creates an administrator, changes one, or changes what reaches one, so no granular scope will ever be enough: the remedy is a token holding authserver:manage, not requesting the route’s scope as for INSUFFICIENT_SCOPE. An administrator is a user, group or client holding any of the six administrative permissions (manage, admin-read, manage-users, manage-clients, manage-settings, browser-sessions), a user directly or through any of its groups, the admin console’s own client whatever it holds, and a client allowed to request the administrative scopes. A granular scope is refused here when it:

  • grants or revokes an administrative permission, directly or by moving a user into or out of a group that holds one, or deletes such a group;
  • writes to an administrator user (profile, credentials, enabled, sessions, consents, attributes, memberships, permissions, deletion), an administrative group (settings, attributes, members, permissions, deletion), or an administrator client (settings, authentication, flows, redirect URIs, web origins, token settings, permissions, logo, deletion);
  • switches whether a client may request the administrative scopes, on or off, on any client;
  • reads an administrator client’s secret;
  • changes the email or the audit-log settings;
  • changes an administrative permission’s description. Granular scopes still read administrators. The refusal is answered after the request’s 400 and 404, writes nothing, carries WWW-Authenticate: Bearer realm="goiabada", error="insufficient_scope", error_description="...", scope="authserver:manage", and leaves an administrator_change_refused audit record.

Every operation this document secures declares a 403. The public client logo endpoint, which carries security: [] and authorises nothing, is the one that does not.

Media typeapplication/json

Admin/account API error envelope. Routing by HTTP status code: 4xx is a client-correctable failure (validation, not found, permission denied, rate limited); 5xx is a server-side problem. The body provides a stable error_code and an error_description for people to read. Protocol endpoints (/auth/token, /auth/authorize, /connect/register, /userinfo) use their RFC-defined error envelopes instead of this one.

object
error_code

Stable identifier for the failure. UPPER_SNAKE for legacy codes (e.g. VALIDATION_ERROR), dotted lowercase for catalog-keyed localized codes (e.g. validator.email.required).

string
error_description
required

A sentence for people to read: in the request’s language when error_code is a catalog key, in English otherwise. Route on error_code, not on this text.

string

Example generated

{
"error_code": "example",
"error_description": "example"
}
WWW-Authenticate
string

Bearer challenge on scope and administrative-policy refusals; other forbidden responses omit it.

The request failed inside the server, most often a storage error. error_code is INTERNAL_SERVER_ERROR: no detail about the failure, only the request id an operator can find the log line by, and retrying is reasonable. Key rotation’s 500 also answers KEY_SET_INCOMPLETE, which names its cause and no retry clears.

Media typeapplication/json

Admin/account API error envelope. Routing by HTTP status code: 4xx is a client-correctable failure (validation, not found, permission denied, rate limited); 5xx is a server-side problem. The body provides a stable error_code and an error_description for people to read. Protocol endpoints (/auth/token, /auth/authorize, /connect/register, /userinfo) use their RFC-defined error envelopes instead of this one.

object
error_code

Stable identifier for the failure. UPPER_SNAKE for legacy codes (e.g. VALIDATION_ERROR), dotted lowercase for catalog-keyed localized codes (e.g. validator.email.required).

string
error_description
required

A sentence for people to read: in the request’s language when error_code is a catalog key, in English otherwise. Route on error_code, not on this text.

string

Example generated

{
"error_code": "example",
"error_description": "example"
}