📧 Outgoing mail
Updated
A queue of messages and a list of SMTP servers. A script only puts the message in the queue; a background sender delivers it separately — a business transaction that rolls back sends no mail.
The SMTP server list#
The Administrator → Mail → SMTP servers page (/smtpservers). One row is
one named account: Host, Port, Security (Auto / None / StartTls /
SslOnConnect), User, Password (or an OAuth access token), From address and
From name, Enabled / Default.
The password is encrypted with ASP.NET Data Protection
(ZuloOne.Smtp.Password) and is never returned by the API. If the
protection keys have changed, the page will say the password has to be entered
again.
The Test button sends a real "ZuloOne SMTP test" message through the chosen account — save the row first, and give it a recipient.
The table: MetaSmtpServers. The name is unique.
Gmail#
- In the Google account, switch 2FA on and create an app password.
- Host
smtp.gmail.com, port587, securityStartTls(or465+SslOnConnect). - The user is the full address (
[email protected]), the password is the app password with no spaces (Google draws it asxxxx xxxx xxxx xxxx). - Accept invalid certificate is not needed for Gmail.
"The operation has timed out" is not a login refusal: Core could not reach the host (CRL/OCSP out of Docker, a firewall on 587). Certificate revocation checks do not block the connection.
Choosing a server from a script#
await context.GetService<IEmailService>().SendAsync(
new EmailMessage
{
AddressTo = "[email protected], [email protected]",
Subject = "Order posted",
Body = "…",
},
new EmailOptions { SmtpServerName = "office365" });
Several recipients in one AddressTo: separated by a comma, a semicolon or
a + b. The name (AddressToName) only makes sense with a single address.
Or EmailOptions.SmtpServerId — the metaId of the SMTP server row.
An attachment from disk: message.AttachFile(filePath, fileName) — the bytes
are read by the platform on the machine the script is running on. A UNC share
is read by the office agent, not by the Core in the cloud. The agent then
passes the already-read bytes to Core through IEmailService.SendAsync
(JSON-RPC) — the file path does not travel to Core. Scheduled reports are the
example.
The order of selection when sending:
- the id given, otherwise the name;
- if id and name are both empty — the enabled server flagged Default;
- otherwise the
Email:*configuration section (Email:Enabled+Email:Host).
The config stays as a fallback channel (a stand with no UI). The server list does not replace it.
The queue#
The Administrator → Mail → Mail queue page (/emailqueue). Send again
only returns the message to the queue — it does not poke SMTP itself. The
"a sender is configured" check is true when there is an enabled server in the
server list or Email:Host is set.
The server-list API: GET/POST /api/email/servers,
PUT/DELETE /api/email/servers/{id}, POST /api/email/servers/{id}/test with
the body { "to": "…" }.