Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ jobs:
run: php tests/params-typed-accessors.php
- name: Captcha posted token
run: php tests/captcha-posted-token.php
# Saving one Server type used to overwrite the only mailserver_* row, so
# switching provider wiped the previous one. Merge/factory helpers are
# pure; a blank posted slot for an inactive type must not replace stored.
- name: Mail server provider presets
run: php tests/mailserver-presets.php
- name: Rewrite param decode
run: php tests/rewrite-param-decode.php
- name: Rewrite route match
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ directory, and removes Google Analytics from the core.

### New

- **Mail Settings remembers each SMTP provider.** Server type is no longer only
Custom or Gmail: Brevo, SMTP2GO and Amazon SES are listed too, with a typical
host and port filled in. Saving one type stores that slot without erasing the
others, so you can switch and Save on the provider that should send. Existing
Custom and Gmail settings keep working.
- CI fails when the committed vendor and asset trees no longer match what
composer.lock and package-lock.json declare. Releases ship those trees verbatim, so
a bump that edits only a manifest would hand users the old library.
Expand Down
27 changes: 21 additions & 6 deletions docs/site/configure/mail-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ Settings live at **Admin → Settings → Mail server**.

## The settings

**Server type** chooses a provider slot. Custom, Gmail, Brevo, SMTP2GO and
Amazon SES each keep their own host, port, username and password. Saving one
does not erase the others — switch the menu to recall a saved provider, then
**Save** on the one that should send. The live `mailserver_*` preferences are
still what `osc_sendMail()` reads; the extra slots are only so you can park a
second provider without retyping it.

Choosing a type fills a typical host and port when that slot is still empty.
Amazon SES uses `email-smtp.us-east-1.amazonaws.com` as a placeholder — change
the region to match your SES account. If outbound port 587 is blocked, Brevo
and SMTP2GO also accept **2525** with `tls`.

| Field | What to enter |
|---|---|
| **Hostname** | Your provider's SMTP host, e.g. `smtp.example.com`. |
Expand Down Expand Up @@ -50,25 +62,28 @@ Without those three, your mail is unauthenticated no matter how it is sent.

Possible, and fine for a small site, but understand the limits: Google caps
daily volume and will mark a classifieds site's alert traffic as suspicious
sooner than a transactional provider would.
sooner than a transactional provider would. Choosing **Gmail Server** fills
`smtp.gmail.com`, port **465**, encryption **ssl** (implicit TLS). Port 587
with `tls` also works if you switch to Custom and enter it by hand.

| Field | Value |
|---|---|
| Hostname | `smtp.gmail.com` |
| Server port | `587` |
| Server port | `465` (or `587` with `tls`) |
| Username | Your full address, e.g. `you@gmail.com` |
| Password | An [app password](https://support.google.com/accounts/answer/185833) — not your account password |
| Encryption | `tls` |
| Encryption | `ssl` on 465, `tls` on 587 |
| SMTP authentication | Checked |

An app password requires 2-Step Verification on the account. Plain account
passwords have not worked for SMTP for years.

## Testing

Trigger a real message rather than guessing — register a test account, or use
the contact form — and watch what happens. If nothing arrives, check the spam
folder before assuming the send failed.
**Send a test email** on the Mail Settings screen sends a message to the
contact address. Prefer that over guessing. If nothing arrives, check the spam
folder before assuming the send failed. A real registration or the contact form
is the next check.

## Troubleshooting

Expand Down
185 changes: 153 additions & 32 deletions oc-admin/themes/modern/settings/mailserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,145 @@
//customize Head
function customHead()
{
$mailType = osc_mailserver_normalize_type(osc_mailserver_type());
$mailPresets = osc_mailserver_presets();
$mailDefaults = array();
foreach (osc_mailserver_allowed_types() as $mailT) {
$mailDefaults[$mailT] = osc_mailserver_type_defaults($mailT);
}
$jsonFlags = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT;
?>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
function fld(name) { return document.querySelector('[name="' + name + '"]'); }

var mailDefaults = <?php echo json_encode($mailDefaults, $jsonFlags); ?>;
var mailPresets = <?php echo json_encode($mailPresets, $jsonFlags); ?>;
var mailType = <?php echo json_encode($mailType, $jsonFlags); ?>;

function readForm() {
return {
host: fld('mailserver_host') ? fld('mailserver_host').value : '',
port: fld('mailserver_port') ? fld('mailserver_port').value : '',
username: fld('mailserver_username') ? fld('mailserver_username').value : '',
password: fld('mailserver_password') ? fld('mailserver_password').value : '',
ssl: fld('mailserver_ssl') ? fld('mailserver_ssl').value : '',
auth: (fld('mailserver_auth') && fld('mailserver_auth').checked) ? '1' : '',
pop: (fld('mailserver_pop') && fld('mailserver_pop').checked) ? '1' : '',
mail_from: fld('mailserver_mail_from') ? fld('mailserver_mail_from').value : '',
name_from: fld('mailserver_name_from') ? fld('mailserver_name_from').value : ''
};
}

function lock(type) {
var locked = (type === 'gmail');
if (fld('mailserver_host')) { fld('mailserver_host').readOnly = locked; }
if (fld('mailserver_port')) { fld('mailserver_port').readOnly = locked; }
}

function pick(slot, defaults, key) {
if (slot && slot[key] !== undefined && slot[key] !== '') {
return slot[key];
}
if (defaults && defaults[key] !== undefined) {
return defaults[key];
}
return '';
}

function apply(type) {
var defaults = mailDefaults[type] || {};
var slot = mailPresets[type] || {};
var host = pick(slot, defaults, 'host');
var port = pick(slot, defaults, 'port');
var ssl = pick(slot, defaults, 'ssl');
if (type === 'gmail') {
host = defaults.host || 'smtp.gmail.com';
port = defaults.port || '465';
ssl = defaults.ssl || 'ssl';
}
if (fld('mailserver_host')) { fld('mailserver_host').value = host; }
if (fld('mailserver_port')) { fld('mailserver_port').value = port; }
if (fld('mailserver_ssl')) { fld('mailserver_ssl').value = ssl; }
if (fld('mailserver_username')) {
fld('mailserver_username').value = pick(slot, defaults, 'username');
}
if (fld('mailserver_password')) {
fld('mailserver_password').value = (slot.password !== undefined) ? slot.password : '';
}
if (fld('mailserver_mail_from')) {
fld('mailserver_mail_from').value = pick(slot, defaults, 'mail_from');
}
if (fld('mailserver_name_from')) {
fld('mailserver_name_from').value = pick(slot, defaults, 'name_from');
}
var authOn = pick(slot, defaults, 'auth');
if (authOn === '') { authOn = '1'; }
var popOn = (slot.pop !== undefined && slot.pop !== '') ? slot.pop : (defaults.pop || '');
if (fld('mailserver_auth')) {
fld('mailserver_auth').checked = (authOn === '1' || authOn === true);
}
if (fld('mailserver_pop')) {
fld('mailserver_pop').checked = (popOn === '1' || popOn === true);
}
lock(type);
}

function stash() {
mailPresets[mailType] = readForm();
var hidden = document.getElementById('mailserver_presets_json');
if (hidden) { hidden.value = JSON.stringify(mailPresets); }
}

var typeSel = document.querySelector('select[name="mailserver_type"]');
if (typeSel) {
lock(mailType);
stash();
typeSel.addEventListener('change', function () {
var host = fld('mailserver_host');
var port = fld('mailserver_port');
if (typeSel.value === 'gmail') {
if (host) { host.value = 'smtp.gmail.com'; host.readOnly = true; }
if (port) { port.value = '465'; port.readOnly = true; }
var u = fld('mailserver_username'); if (u) { u.value = ''; }
var pw = fld('mailserver_password'); if (pw) { pw.value = ''; }
var ssl = fld('mailserver_ssl'); if (ssl) { ssl.value = 'ssl'; }
var auth = fld('mailserver_auth'); if (auth) { auth.checked = true; }
var pop = fld('mailserver_pop'); if (pop) { pop.checked = false; }
} else {
if (host) { host.readOnly = false; }
if (port) { port.readOnly = false; }
}
stash();
mailType = typeSel.value;
apply(mailType);
stash();
});
}

var form = document.querySelector('form[name="settings_form"]');
if (form) {
form.addEventListener('submit', function () { stash(); });
}

var testBtn = document.getElementById('testMail');
if (testBtn) {
testBtn.addEventListener('click', function () {
fetch("<?php echo osc_admin_base_url(true)?>?page=ajax&action=test_mail", {
var msg = document.getElementById('testMail_message');
var pEl = msg ? msg.querySelector('p') : null;
function show(html, ok) {
if (!msg || !pEl) { return; }
pEl.textContent = html;
msg.style.display = 'block';
msg.classList.remove('flashmessage-ok', 'flashmessage-error');
msg.classList.add(ok ? 'flashmessage-ok' : 'flashmessage-error');
}
var controller = (typeof AbortController === 'function') ? new AbortController() : null;
var timer = window.setTimeout(function () {
if (controller) { controller.abort(); }
}, 25000);
var opts = {
credentials: 'same-origin',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
}).then(function (r) {
return r.json();
}).then(function (data) {
var msg = document.getElementById('testMail_message');
if (!msg) { return; }
var pEl = msg.querySelector('p');
if (pEl) { pEl.innerHTML = data.html; }
msg.style.display = 'block';
msg.classList.add(data.status == 1 ? 'ok' : 'error');
});
};
if (controller) { opts.signal = controller.signal; }
fetch("<?php echo osc_admin_base_url(true)?>?page=ajax&action=test_mail", opts)
.then(function (r) { return r.json(); })
.then(function (data) {
show(data.html, data.status == 1);
})
.catch(function () {
show(<?php echo json_encode(
__('Mail server did not respond in time. Check host, port and encryption, and that POP before SMTP is off.')
); ?>, false);
})
.then(function () { window.clearTimeout(timer); });
});
}
});
Expand All @@ -82,7 +180,9 @@ function render_offset()
. ", check your hosting's help section."),
));

osc_current_admin_theme_path('parts/header.php'); ?>
osc_current_admin_theme_path('parts/header.php');
$mailTypeNow = osc_mailserver_normalize_type(osc_mailserver_type());
?>
<div id="mail-setting">
<!-- settings form -->
<div id="mail-settings">
Expand All @@ -91,24 +191,34 @@ function render_offset()
<form name="settings_form" action="<?php echo osc_admin_base_url(true); ?>" method="post">
<input type="hidden" name="page" value="settings"/>
<input type="hidden" name="action" value="mailserver_post"/>
<input type="hidden" name="mailserver_presets_json" id="mailserver_presets_json"
value="<?php echo osc_esc_html(json_encode(osc_mailserver_presets())); ?>"/>
<fieldset>
<div class="form-horizontal">
<div class="form-row">
<div class="form-label"><?php _e('Server type'); ?></div>
<div class="form-controls">
<select class="form-select form-select-sm " name="mailserver_type">
<option value="custom" <?php echo (osc_mailserver_type() === 'custom')
<option value="custom" <?php echo ($mailTypeNow === 'custom')
? 'selected="true"' : ''; ?>><?php _e('Custom Server'); ?></option>
<option value="gmail" <?php echo (osc_mailserver_type() === 'gmail') ? 'selected="true"'
: ''; ?>><?php _e('GMail Server'); ?></option>
<option value="gmail" <?php echo ($mailTypeNow === 'gmail')
? 'selected="true"' : ''; ?>><?php _e('GMail Server'); ?></option>
<option value="brevo" <?php echo ($mailTypeNow === 'brevo')
? 'selected="true"' : ''; ?>><?php _e('Brevo'); ?></option>
<option value="smtp2go" <?php echo ($mailTypeNow === 'smtp2go')
? 'selected="true"' : ''; ?>><?php _e('SMTP2GO'); ?></option>
<option value="ses" <?php echo ($mailTypeNow === 'ses')
? 'selected="true"' : ''; ?>><?php _e('Amazon SES'); ?></option>
</select>
<div class="help-box"><?php _e('Each type is stored separately. Saving Brevo does not erase Gmail or SMTP2GO. Switch the menu to recall a saved provider, then Save on the one that should send.'); ?></div>
</div>
</div>
<div class="form-row">
<div class="form-label"><?php _e('Hostname'); ?></div>
<div class="form-controls">
<input type="text" class="input-large" name="mailserver_host"
value="<?php echo osc_esc_html(osc_mailserver_host()); ?>"/>
<div class="help-box"><?php _e('Gmail, Brevo, SMTP2GO and Amazon SES fill a typical host and port. For SES, change the region in the hostname (email-smtp.{region}.amazonaws.com) to match your account. If port 587 is blocked, Brevo and SMTP2GO also accept 2525 with tls.'); ?></div>
</div>
</div>
<div class="form-row">
Expand Down Expand Up @@ -151,7 +261,7 @@ function render_offset()
<div class="form-controls">
<input type="text" class="input-medium" name="mailserver_ssl"
value="<?php echo osc_esc_html(osc_mailserver_ssl()); ?>"/>
<?php _e('Options: blank, ssl or tls'); ?>
<?php _e('Options: blank, ssl or tls. Gmail uses ssl on 465. Brevo, SMTP2GO and SES typically use tls.'); ?>
<?php if (PHP_SAPI === 'cgi-fcgi' || PHP_SAPI === 'cgi') { ?>
<div class="callout-warning">
<p><?php _e('Cannot be sure that Apache Module <b>mod_ssl</b> is loaded.'); ?></p>
Expand All @@ -176,10 +286,21 @@ function render_offset()
<div class="form-controls">
<div class="form-label-checkbox"><input type="checkbox" <?php echo(osc_mailserver_pop()
? 'checked="checked"' : ''); ?> name="mailserver_pop" value="1"/>
<?php _e('Use POP before SMTP'); ?></div>
<?php _e('Use POP before SMTP'); ?>
<span class="help-box"><?php _e('Leave unchecked for Amazon SES, SMTP2GO, Brevo, and most SMTP hosts.'); ?></span>
</div>
</div>
</div>
<?php osc_admin_form_actions(); ?>
<div id="testMail_message" class="flashmessage" style="display:none"><p></p></div>
<?php osc_admin_form_actions(array(
array('label' => __('Save changes'), 'type' => 'submit', 'variant' => 'primary'),
array(
'label' => __('Send a test email'),
'type' => 'button',
'variant' => 'secondary',
'attrs' => array('id' => 'testMail'),
),
)); ?>
</div>
</fieldset>
</form>
Expand Down
Loading
Loading