Errors¶
A problem shows up at one of two moments.
Right away¶
The native returns false or 0 — a malformed address, no account, a full queue, a file that cannot be read. Ask what happened:
if (!email_send_to(address, "Hello", body))
{
switch (email_errno())
{
case EMAIL_ERROR_INVALID_ADDRESS:
SendClientMessage(playerid, -1, "That email address is not valid.");
case EMAIL_ERROR_QUEUE_FULL:
SendClientMessage(playerid, -1, "Mail is busy, try again in a minute.");
default:
SendClientMessage(playerid, -1, "Mail is unavailable right now.");
}
}
email_error(account, dest) writes the message behind the code, which is usually the relay's own reply.
email_errno() reads the default account. Before any account is open it reads the global slot instead, which is where failures from before one existed land — an unreadable config file, a malformed sender address.
Later, when the relay answers¶
OnEmailError runs in every loaded script, and your send callback runs with success = 0. Use the callback to tell the player, and this to log the reason:
public OnEmailError(account, const recipient[], const callback[], const error[], errorid)
{
printf("[email] account %d, <%s>, error %d: %s", account, recipient, errorid, error);
return 1;
}
It fires once, after any retries: a temporary failure is tried again before you hear about it, so an error here is final.
OnEmailSent is the counterpart, for every message that leaves — useful for a filterscript that keeps the mail log without the gamemode passing a callback to each send.
Both are broadcasts, but only to the scripts that define them: which scripts those are is worked out once when each script loads, not per message. A gamemode plus a dozen filterscripts costs nothing for the ones that do not define the callback.
Whatever you do write there runs on the main thread, once per message. Keep it to a log line; a mailing of five thousand runs it five thousand times.
The codes¶
| Code | Means |
|---|---|
EMAIL_ERROR_NONE |
no error |
EMAIL_ERROR_INVALID_ACCOUNT |
the account was never opened, or is closed |
EMAIL_ERROR_INVALID_MESSAGE |
the message handle is unknown, or was already sent |
EMAIL_ERROR_TEMPLATE_FAILED |
the template file could not be read |
EMAIL_ERROR_INVALID_ADDRESS |
not a valid mailbox |
EMAIL_ERROR_CONNECTION_FAILED |
unreachable, refused, timed out, or TLS failed |
EMAIL_ERROR_AUTH_FAILED |
wrong credentials, or no mechanism in common |
EMAIL_ERROR_SEND_FAILED |
the relay took the session and refused the message |
EMAIL_ERROR_BUILD_FAILED |
the message could not be assembled, or hit a limit |
EMAIL_ERROR_ATTACHMENT_FAILED |
the file is unusable: unreadable, too large, bad MIME type |
EMAIL_ERROR_HEADER_INJECTION |
a line break in a value that becomes a header |
EMAIL_ERROR_CONFIG_FAILED |
the configuration is missing, unreadable or wrong |
EMAIL_ERROR_QUEUE_FULL |
the account's queue is full; the message was not consumed |
What goes where¶
The console gets a short line with a code. Everything else — the recipient's address, the relay's wording — goes only to logs/email.log, which rotates at 50 MB into gzipped archives. email_log(EMAIL_LOG_WARNING) changes how much either gets, at runtime.
That split is deliberate: a console is read by whoever is nearby, and a bounce message routinely contains a player's address.
Checking the setup at startup¶
email_setup does not dial anything — it builds the transport, and the first TCP + TLS + AUTH happens on the first send. To find out sooner:
email_test(0, "OnMailTested");
Email::OnMailTested(success)
{
new status[128];
email_status(0, status); // "smtp.gmail.com:587 STARTTLS"
printf("[email] %s: %s", status, success ? ("working") : ("NOT working"));
return 1;
}
email_test opens a session, authenticates and closes it, on a worker thread like every send. A wrong password shows up in the console while you are watching, instead of on the first player's registration.