Per app, once. The shared domain, DNS records and policy come first:
Provision § Email delivery. Why it’s shaped this way:
Concepts § Email. No manifest changes: the app already reads its settings from Infisical via
envFrom, and nothing in the cluster blocks outbound port 587.
Below, <app> is the lowercase name (talon), <App> the display name (Talon). Every OCI step is in region
af-johannesburg-1 (top right of the console).
1. Notices alias
Section titled “1. Notices alias”On the oci mailbox, at whichever provider hosts it. This setup uses Zoho Mail.
In the browser (Zoho: mailadmin.zoho.com).
- Users → the oci mailbox → Email alias → Add →
oci-<app>→ save.
OCI emails its account notices for this app’s user to oci-<app>@visarend.systems. It is not a sender.
2. Compartment and approved sender (OCI)
Section titled “2. Compartment and approved sender (OCI)”In the browser (cloud.oracle.com).
- ☰ → Identity & Security → Compartments → Create Compartment. Name
email-<app>, description<App> sender, parent email. - ☰ → Developer Services → Email Delivery → Approved Senders. Compartment picker (left): email → email-
. - Create Approved Sender →
<app>@mail.visarend.systems.
Success: the sender is listed under email-<app> (switch the picker to check), status Active.
3. User, group and policy line (OCI)
Section titled “3. User, group and policy line (OCI)”In the browser.
-
☰ → Identity & Security → Domains, compartment root, domain Default → User management → Users → Create user. First name
SMTP, last name<App>, emailoci-<app>@visarend.systems, no admin roles. Ignore the activation email: this user never logs in. -
User management → Groups → Create group
email-<app>, member SMTP<App>. -
☰ → Identity & Security → Policies, compartment root → email-apps-send → Edit Policy Statements → add:
Allow group 'Default'/'email-<app>' to use approved-senders in compartment email:email-<app>
Success: the group lists the user; the policy saves. Allow about 5 minutes before step 5.
4. SMTP credentials into Infisical
Section titled “4. SMTP credentials into Infisical”The password is shown once. Open the app’s project at vault.webbies.dev first.
In the browser.
- OCI: Domains → Default → Users → SMTP
<App>→ SMTP credentials (under Resources, or a tab). If it’s missing: Edit user capabilities → tick SMTP credentials → save. - Generate credentials, description
<app> app. Copy the username (long,ocid1.user.oc1..…@ocid1.tenancy.oc1..…) and the password. - Infisical, project
<app>, staging and prod (and dev only to test real delivery locally):
| Key | Value |
|---|---|
SMTP_HOST | smtp.email.af-johannesburg-1.oci.oraclecloud.com |
SMTP_PORT | 587 |
SMTP_USERNAME | username from step 2 |
SMTP_PASSWORD | password from step 2 |
MAIL_FROM | <app>@mail.visarend.systems |
Paste values without a trailing space or line break (don’t press Enter after pasting). These are the standard names; an app that reads other names gets the same values under those.
5. Prove it sends, before the app is involved
Section titled “5. Prove it sends, before the app is involved”In the browser: OCI Cloud Shell (>_, top right). Replace YOU@example.com with any inbox you can read, paste
the username and password when asked:
python3 -c "import smtplib, getpasss = smtplib.SMTP('smtp.email.af-johannesburg-1.oci.oraclecloud.com', 587); s.starttls()s.login(input('Username: '), getpass.getpass('Password: '))s.sendmail('<app>@mail.visarend.systems', ['YOU@example.com'], 'Subject: <App> OCI test\r\n\r\nIt works.')print('SENT')"Success: prints SENT and the mail arrives, and its Authentication-Results header shows spf=pass,
dkim=pass and dmarc=pass (in Gmail: ⋮ → Show original). Anything else: Troubleshooting § OCI email.
The first mails from a new sender can land in spam; mark them “Not spam”.
6. Restart the app
Section titled “6. Restart the app”The pod reads its settings only when it starts. The operator syncs Infisical into the app’s Secret every 60 seconds. Wait a minute after step 4, then restart the pod: Operate rollouts → Force a restart, or delete the pod in Argo CD. Then trigger a real email from the app.
What the app’s mail client must do
Section titled “What the app’s mail client must do”Any language, any library. Invoicer’s internal/mail package is the reference implementation.
- STARTTLS on 587. Port 25 is blocked on OCI; don’t use implicit TLS on 465.
- AUTH
PLAINorLOGIN, whichever the server offers. Go’ssmtp.PlainAuthalone isn’t enough: pick from the server’sAUTHlist. - Envelope sender =
MAIL_FROM, exactly the approved sender. A display name (“Rudo Hair Studio via Invoicer™”) is fine; Reply-To can be anything. - Trim the settings when reading them, so a stray line break from a paste can’t break the login.
- A send timeout (Invoicer uses 20s), so a slow server can’t hang a request.
- No addresses in logs. SMTP replies after login can quote recipients; log the status code. The login reply is safe to log in full and names the cause.
- Message size. OCI’s limit is about 2 MB including encoding, so attachments up to about 1.4 MB. Request a
max-message-sizeincrease if the app attaches more (see Provision). - Every recipient counts against the tenancy’s daily limit (each started 2 MB, per To/Cc/Bcc address). An app that
lets users send should cap its own daily volume, as Invoicer does with
EMAIL_DAILY_CAP.
Keycloak
Section titled “Keycloak”Keycloak sends realm mail (password resets, execute-actions-email, email verification) the same way. Its identity is
set up once for both Keycloak instances, with sender auth@mail.visarend.systems (the one exception to the
<app>@ pattern, because users see it on password resets):
| Step | Value |
|---|---|
| Notices alias | oci-keycloak |
| Compartment | email-keycloak |
| Sender | auth@mail.visarend.systems, in email-keycloak |
| User / group | SMTP Keycloak (oci-keycloak@visarend.systems) / email-keycloak |
| Policy line | Allow group 'Default'/'email-keycloak' to use approved-senders in compartment email:email-keycloak |
Keycloak doesn’t read Infisical. Its SMTP settings are per realm, stored in its database (backed up with
keycloak-pg), not in Git.
In the browser, for each Keycloak (https://uat.auth.webbies.dev/admin, https://auth.webbies.dev/admin) and each
realm that sends mail:
- Your admin user needs an email address first (Users → you → Email), because Test connection sends to it.
- Realm settings → Email.
- From:
auth@mail.visarend.systems. From display name: the product the realm serves (for examplePenvoice). Reply to: optional, a real mailbox. - Host:
smtp.email.af-johannesburg-1.oci.oraclecloud.com, Port:587. - Encryption: Enable StartTLS on, Enable SSL off.
- Authentication: on, type password. Username / Password: the
SMTP Keycloakcredentials.
- From:
- Save, then Test connection.
Success: the test mail arrives. Only then turn on realm features that depend on mail (for example verifyEmail in
the realm YAML).