Lisää SMTP-lähetystuki postilaatikoihin
Aiemmin sähköpostit lähetettiin PHP mail()-funktiolla, mikä ei toimi kunnolla useimmilla palvelimilla (SPF/DKIM-ongelmat). Nyt mailboxiin voi konfiguroida SMTP-asetukset (host, port, user, pass, encryption), ja lähetys tapahtuu suoraan SMTP-palvelimen kautta socket-yhteydellä. Fallback PHP mail():iin jos SMTP-asetuksia ei ole asetettu. - db.php: smtp_host/port/user/password/encryption sarakkeet - api.php: sendViaSMTP() socket-pohjainen SMTP-client - index.html: SMTP-kentät mailbox-lomakkeeseen - script.js: SMTP-kenttien luku/kirjoitus lomakkeessa Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
137
api.php
137
api.php
@@ -572,6 +572,13 @@ function sendTicketMail(string $to, string $subject, string $body, string $inRep
|
||||
$fromEmail = $mailbox['smtp_from_email'] ?? $mailbox['imap_user'] ?? MAIL_FROM;
|
||||
$fromName = $mailbox['smtp_from_name'] ?? $mailbox['nimi'] ?? 'Asiakaspalvelu';
|
||||
|
||||
// Jos mailboxilla on SMTP-asetukset, käytä SMTP:tä
|
||||
$smtpHost = $mailbox['smtp_host'] ?? '';
|
||||
if ($smtpHost !== '') {
|
||||
return sendViaSMTP($to, $subject, $body, $fromEmail, $fromName, $inReplyTo, $references, $mailbox, $cc);
|
||||
}
|
||||
|
||||
// Fallback: PHP mail()
|
||||
$headers = "MIME-Version: 1.0\r\n";
|
||||
$headers .= "Content-Type: text/plain; charset=UTF-8\r\n";
|
||||
$headers .= "From: {$fromName} <{$fromEmail}>\r\n";
|
||||
@@ -586,6 +593,118 @@ function sendTicketMail(string $to, string $subject, string $body, string $inRep
|
||||
return mail($to, $subject, $body, $headers, '-f ' . $fromEmail);
|
||||
}
|
||||
|
||||
function sendViaSMTP(string $to, string $subject, string $body, string $fromEmail, string $fromName, string $inReplyTo, string $references, array $mailbox, string $cc): bool {
|
||||
$host = $mailbox['smtp_host'];
|
||||
$port = (int)($mailbox['smtp_port'] ?? 587);
|
||||
$user = $mailbox['smtp_user'] ?? '';
|
||||
$pass = $mailbox['smtp_password'] ?? '';
|
||||
$encryption = $mailbox['smtp_encryption'] ?? 'tls';
|
||||
|
||||
$timeout = 15;
|
||||
$errno = 0; $errstr = '';
|
||||
|
||||
// Yhteys
|
||||
if ($encryption === 'ssl') {
|
||||
$fp = @stream_socket_client("ssl://{$host}:{$port}", $errno, $errstr, $timeout);
|
||||
} else {
|
||||
$fp = @stream_socket_client("tcp://{$host}:{$port}", $errno, $errstr, $timeout);
|
||||
}
|
||||
if (!$fp) {
|
||||
error_log("SMTP connect failed: {$errstr} ({$errno})");
|
||||
return false;
|
||||
}
|
||||
stream_set_timeout($fp, $timeout);
|
||||
|
||||
$resp = fgets($fp, 512);
|
||||
if (substr($resp, 0, 3) !== '220') { fclose($fp); error_log("SMTP banner: $resp"); return false; }
|
||||
|
||||
// EHLO
|
||||
fwrite($fp, "EHLO " . gethostname() . "\r\n");
|
||||
$ehloResp = '';
|
||||
while ($line = fgets($fp, 512)) {
|
||||
$ehloResp .= $line;
|
||||
if (substr($line, 3, 1) === ' ') break;
|
||||
}
|
||||
|
||||
// STARTTLS jos tls
|
||||
if ($encryption === 'tls') {
|
||||
fwrite($fp, "STARTTLS\r\n");
|
||||
$resp = fgets($fp, 512);
|
||||
if (substr($resp, 0, 3) !== '220') { fclose($fp); error_log("SMTP STARTTLS: $resp"); return false; }
|
||||
$crypto = stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT);
|
||||
if (!$crypto) { fclose($fp); error_log("SMTP TLS negotiation failed"); return false; }
|
||||
// EHLO uudelleen TLS:n jälkeen
|
||||
fwrite($fp, "EHLO " . gethostname() . "\r\n");
|
||||
while ($line = fgets($fp, 512)) {
|
||||
if (substr($line, 3, 1) === ' ') break;
|
||||
}
|
||||
}
|
||||
|
||||
// AUTH LOGIN
|
||||
if ($user !== '') {
|
||||
fwrite($fp, "AUTH LOGIN\r\n");
|
||||
$resp = fgets($fp, 512);
|
||||
if (substr($resp, 0, 3) !== '334') { fclose($fp); error_log("SMTP AUTH: $resp"); return false; }
|
||||
fwrite($fp, base64_encode($user) . "\r\n");
|
||||
$resp = fgets($fp, 512);
|
||||
if (substr($resp, 0, 3) !== '334') { fclose($fp); error_log("SMTP AUTH user: $resp"); return false; }
|
||||
fwrite($fp, base64_encode($pass) . "\r\n");
|
||||
$resp = fgets($fp, 512);
|
||||
if (substr($resp, 0, 3) !== '235') { fclose($fp); error_log("SMTP AUTH pass: $resp"); return false; }
|
||||
}
|
||||
|
||||
// MAIL FROM
|
||||
fwrite($fp, "MAIL FROM:<{$fromEmail}>\r\n");
|
||||
$resp = fgets($fp, 512);
|
||||
if (substr($resp, 0, 3) !== '250') { fclose($fp); error_log("SMTP MAIL FROM: $resp"); return false; }
|
||||
|
||||
// RCPT TO
|
||||
$allRecipients = array_filter(array_map('trim', explode(',', $to)));
|
||||
if ($cc) {
|
||||
$allRecipients = array_merge($allRecipients, array_filter(array_map('trim', explode(',', $cc))));
|
||||
}
|
||||
foreach ($allRecipients as $rcpt) {
|
||||
fwrite($fp, "RCPT TO:<{$rcpt}>\r\n");
|
||||
$resp = fgets($fp, 512);
|
||||
if (substr($resp, 0, 3) !== '250' && substr($resp, 0, 3) !== '251') {
|
||||
fclose($fp); error_log("SMTP RCPT TO: $resp"); return false;
|
||||
}
|
||||
}
|
||||
|
||||
// DATA
|
||||
fwrite($fp, "DATA\r\n");
|
||||
$resp = fgets($fp, 512);
|
||||
if (substr($resp, 0, 3) !== '354') { fclose($fp); error_log("SMTP DATA: $resp"); return false; }
|
||||
|
||||
// Rakennetaan viesti
|
||||
$messageId = '<' . uniqid('msg_', true) . '@' . (explode('@', $fromEmail)[1] ?? 'localhost') . '>';
|
||||
$msg = "From: {$fromName} <{$fromEmail}>\r\n";
|
||||
$msg .= "To: {$to}\r\n";
|
||||
if ($cc) $msg .= "Cc: {$cc}\r\n";
|
||||
$msg .= "Subject: =?UTF-8?B?" . base64_encode($subject) . "?=\r\n";
|
||||
$msg .= "Message-ID: {$messageId}\r\n";
|
||||
if ($inReplyTo) {
|
||||
$msg .= "In-Reply-To: {$inReplyTo}\r\n";
|
||||
$msg .= "References: " . ($references ? $references . ' ' : '') . $inReplyTo . "\r\n";
|
||||
}
|
||||
$msg .= "MIME-Version: 1.0\r\n";
|
||||
$msg .= "Content-Type: text/plain; charset=UTF-8\r\n";
|
||||
$msg .= "Content-Transfer-Encoding: base64\r\n";
|
||||
$msg .= "Date: " . date('r') . "\r\n";
|
||||
$msg .= "\r\n";
|
||||
$msg .= chunk_split(base64_encode($body));
|
||||
$msg .= "\r\n.\r\n";
|
||||
|
||||
fwrite($fp, $msg);
|
||||
$resp = fgets($fp, 512);
|
||||
if (substr($resp, 0, 3) !== '250') { fclose($fp); error_log("SMTP send: $resp"); return false; }
|
||||
|
||||
// QUIT
|
||||
fwrite($fp, "QUIT\r\n");
|
||||
fclose($fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
function parseLiittymat(array $input): array {
|
||||
$liittymat = [];
|
||||
foreach (($input['liittymat'] ?? []) as $l) {
|
||||
@@ -2580,6 +2699,7 @@ switch ($action) {
|
||||
$result[] = [
|
||||
'id' => $mb['id'],
|
||||
'nimi' => $mb['nimi'] ?? $mb['imap_user'] ?? '',
|
||||
'smtp_from_email' => $mb['smtp_from_email'] ?? $mb['imap_user'] ?? '',
|
||||
'company_id' => $comp['id'],
|
||||
'company_nimi' => $comp['nimi'],
|
||||
];
|
||||
@@ -2755,6 +2875,7 @@ switch ($action) {
|
||||
// Palauta postilaatikot ilman salasanoja
|
||||
$mbs = array_map(function($mb) {
|
||||
$mb['imap_password'] = !empty($mb['imap_password']) ? '********' : '';
|
||||
$mb['smtp_password'] = !empty($mb['smtp_password']) ? '********' : '';
|
||||
return $mb;
|
||||
}, $mailboxes);
|
||||
echo json_encode($mbs);
|
||||
@@ -2775,16 +2896,26 @@ switch ($action) {
|
||||
'imap_encryption' => trim($input['imap_encryption'] ?? 'ssl'),
|
||||
'smtp_from_email' => trim($input['smtp_from_email'] ?? ''),
|
||||
'smtp_from_name' => trim($input['smtp_from_name'] ?? ''),
|
||||
'smtp_host' => trim($input['smtp_host'] ?? ''),
|
||||
'smtp_port' => intval($input['smtp_port'] ?? 587),
|
||||
'smtp_user' => trim($input['smtp_user'] ?? ''),
|
||||
'smtp_encryption' => trim($input['smtp_encryption'] ?? 'tls'),
|
||||
'aktiivinen' => $input['aktiivinen'] ?? true,
|
||||
];
|
||||
// Salasana: jos ******** -> pidä vanha, muuten päivitä
|
||||
// Hae vanha mailbox salasanojen vertailua varten
|
||||
$existingMb = dbGetMailbox($mb['id']);
|
||||
// IMAP-salasana: jos ******** -> pidä vanha, muuten päivitä
|
||||
if (isset($input['imap_password']) && $input['imap_password'] !== '********') {
|
||||
$mb['imap_password'] = $input['imap_password'];
|
||||
} else {
|
||||
// Hae vanha salasana
|
||||
$existingMb = dbGetMailbox($mb['id']);
|
||||
$mb['imap_password'] = $existingMb ? ($existingMb['imap_password'] ?? '') : '';
|
||||
}
|
||||
// SMTP-salasana: jos ******** -> pidä vanha, muuten päivitä
|
||||
if (isset($input['smtp_password']) && $input['smtp_password'] !== '********') {
|
||||
$mb['smtp_password'] = $input['smtp_password'];
|
||||
} else {
|
||||
$mb['smtp_password'] = $existingMb ? ($existingMb['smtp_password'] ?? '') : '';
|
||||
}
|
||||
|
||||
if (empty($mb['nimi'])) {
|
||||
http_response_code(400);
|
||||
|
||||
24
db.php
24
db.php
@@ -309,6 +309,11 @@ function initDatabase(): void {
|
||||
imap_user VARCHAR(255),
|
||||
imap_encryption VARCHAR(10) DEFAULT 'ssl',
|
||||
imap_password VARCHAR(255),
|
||||
smtp_host VARCHAR(255) DEFAULT '',
|
||||
smtp_port INT DEFAULT 587,
|
||||
smtp_user VARCHAR(255) DEFAULT '',
|
||||
smtp_password VARCHAR(255) DEFAULT '',
|
||||
smtp_encryption VARCHAR(10) DEFAULT 'tls',
|
||||
smtp_from_email VARCHAR(255),
|
||||
smtp_from_name VARCHAR(255),
|
||||
aktiivinen BOOLEAN DEFAULT TRUE,
|
||||
@@ -431,6 +436,11 @@ function initDatabase(): void {
|
||||
"ALTER TABLE customer_connections ADD COLUMN laite VARCHAR(100) DEFAULT '' AFTER vlan",
|
||||
"ALTER TABLE customer_connections ADD COLUMN portti VARCHAR(100) DEFAULT '' AFTER laite",
|
||||
"ALTER TABLE customer_connections ADD COLUMN ip VARCHAR(100) DEFAULT '' AFTER portti",
|
||||
"ALTER TABLE mailboxes ADD COLUMN smtp_host VARCHAR(255) DEFAULT '' AFTER smtp_from_name",
|
||||
"ALTER TABLE mailboxes ADD COLUMN smtp_port INT DEFAULT 587 AFTER smtp_host",
|
||||
"ALTER TABLE mailboxes ADD COLUMN smtp_user VARCHAR(255) DEFAULT '' AFTER smtp_port",
|
||||
"ALTER TABLE mailboxes ADD COLUMN smtp_password VARCHAR(255) DEFAULT '' AFTER smtp_user",
|
||||
"ALTER TABLE mailboxes ADD COLUMN smtp_encryption VARCHAR(10) DEFAULT 'tls' AFTER smtp_password",
|
||||
];
|
||||
foreach ($alters as $sql) {
|
||||
try { $db->query($sql); } catch (\Throwable $e) { /* sarake on jo olemassa / jo ajettu */ }
|
||||
@@ -1148,6 +1158,7 @@ function dbLoadMailboxes(string $companyId): array {
|
||||
foreach ($boxes as &$b) {
|
||||
$b['aktiivinen'] = (bool)$b['aktiivinen'];
|
||||
$b['imap_port'] = (int)$b['imap_port'];
|
||||
$b['smtp_port'] = (int)($b['smtp_port'] ?? 587);
|
||||
unset($b['company_id']);
|
||||
}
|
||||
return $boxes;
|
||||
@@ -1155,13 +1166,15 @@ function dbLoadMailboxes(string $companyId): array {
|
||||
|
||||
function dbSaveMailbox(string $companyId, array $mailbox): void {
|
||||
_dbExecute("
|
||||
INSERT INTO mailboxes (id, company_id, nimi, imap_host, imap_port, imap_user, imap_encryption, imap_password, smtp_from_email, smtp_from_name, aktiivinen)
|
||||
VALUES (:id, :company_id, :nimi, :imap_host, :imap_port, :imap_user, :imap_encryption, :imap_password, :smtp_from_email, :smtp_from_name, :aktiivinen)
|
||||
INSERT INTO mailboxes (id, company_id, nimi, imap_host, imap_port, imap_user, imap_encryption, imap_password, smtp_from_email, smtp_from_name, smtp_host, smtp_port, smtp_user, smtp_password, smtp_encryption, aktiivinen)
|
||||
VALUES (:id, :company_id, :nimi, :imap_host, :imap_port, :imap_user, :imap_encryption, :imap_password, :smtp_from_email, :smtp_from_name, :smtp_host, :smtp_port, :smtp_user, :smtp_password, :smtp_encryption, :aktiivinen)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
nimi = VALUES(nimi), imap_host = VALUES(imap_host), imap_port = VALUES(imap_port),
|
||||
imap_user = VALUES(imap_user), imap_encryption = VALUES(imap_encryption),
|
||||
imap_password = VALUES(imap_password), smtp_from_email = VALUES(smtp_from_email),
|
||||
smtp_from_name = VALUES(smtp_from_name), aktiivinen = VALUES(aktiivinen)
|
||||
smtp_from_name = VALUES(smtp_from_name), smtp_host = VALUES(smtp_host), smtp_port = VALUES(smtp_port),
|
||||
smtp_user = VALUES(smtp_user), smtp_password = VALUES(smtp_password),
|
||||
smtp_encryption = VALUES(smtp_encryption), aktiivinen = VALUES(aktiivinen)
|
||||
", [
|
||||
'id' => $mailbox['id'],
|
||||
'company_id' => $companyId,
|
||||
@@ -1173,6 +1186,11 @@ function dbSaveMailbox(string $companyId, array $mailbox): void {
|
||||
'imap_password' => $mailbox['imap_password'] ?? '',
|
||||
'smtp_from_email' => $mailbox['smtp_from_email'] ?? '',
|
||||
'smtp_from_name' => $mailbox['smtp_from_name'] ?? '',
|
||||
'smtp_host' => $mailbox['smtp_host'] ?? '',
|
||||
'smtp_port' => $mailbox['smtp_port'] ?? 587,
|
||||
'smtp_user' => $mailbox['smtp_user'] ?? '',
|
||||
'smtp_password' => $mailbox['smtp_password'] ?? '',
|
||||
'smtp_encryption' => $mailbox['smtp_encryption'] ?? 'tls',
|
||||
'aktiivinen' => $mailbox['aktiivinen'] ?? true,
|
||||
]);
|
||||
}
|
||||
|
||||
26
index.html
26
index.html
@@ -835,6 +835,32 @@
|
||||
<label>Lähettäjän nimi</label>
|
||||
<input type="text" id="mailbox-form-smtp-name" placeholder="Yritys Asiakaspalvelu">
|
||||
</div>
|
||||
<h4 style="margin:1rem 0 0.5rem;color:#0f3460;font-size:0.9rem;">SMTP-lähetysasetukset</h4>
|
||||
<p style="font-size:0.78rem;color:#888;margin-bottom:0.5rem;">Jätä tyhjäksi käyttääksesi palvelimen omaa sendmailia</p>
|
||||
<div class="form-group">
|
||||
<label>SMTP-palvelin</label>
|
||||
<input type="text" id="mailbox-form-smtp-host" placeholder="mail.yritys.fi">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>SMTP-portti</label>
|
||||
<input type="number" id="mailbox-form-smtp-port" value="587" placeholder="587">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>SMTP-käyttäjä</label>
|
||||
<input type="text" id="mailbox-form-smtp-user" placeholder="asiakaspalvelu@yritys.fi">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>SMTP-salasana</label>
|
||||
<input type="password" id="mailbox-form-smtp-pass" placeholder="••••••••">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>SMTP-salaus</label>
|
||||
<select id="mailbox-form-smtp-encryption">
|
||||
<option value="tls">STARTTLS</option>
|
||||
<option value="ssl">SSL</option>
|
||||
<option value="none">Ei salausta</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:0.5rem;margin-top:0.75rem;">
|
||||
<button class="btn-primary" id="btn-save-mailbox">Tallenna</button>
|
||||
|
||||
10
script.js
10
script.js
@@ -2527,6 +2527,11 @@ function showMailboxForm(mb = null) {
|
||||
document.getElementById('mailbox-form-encryption').value = mb ? (mb.imap_encryption || 'ssl') : 'ssl';
|
||||
document.getElementById('mailbox-form-smtp-email').value = mb ? (mb.smtp_from_email || '') : '';
|
||||
document.getElementById('mailbox-form-smtp-name').value = mb ? (mb.smtp_from_name || '') : '';
|
||||
document.getElementById('mailbox-form-smtp-host').value = mb ? (mb.smtp_host || '') : '';
|
||||
document.getElementById('mailbox-form-smtp-port').value = mb ? (mb.smtp_port || 587) : 587;
|
||||
document.getElementById('mailbox-form-smtp-user').value = mb ? (mb.smtp_user || '') : '';
|
||||
document.getElementById('mailbox-form-smtp-pass').value = mb ? (mb.smtp_password || '') : '';
|
||||
document.getElementById('mailbox-form-smtp-encryption').value = mb ? (mb.smtp_encryption || 'tls') : 'tls';
|
||||
document.getElementById('mailbox-form-container').style.display = '';
|
||||
}
|
||||
|
||||
@@ -2554,6 +2559,11 @@ document.getElementById('btn-save-mailbox').addEventListener('click', async () =
|
||||
imap_encryption: document.getElementById('mailbox-form-encryption').value,
|
||||
smtp_from_email: document.getElementById('mailbox-form-smtp-email').value,
|
||||
smtp_from_name: document.getElementById('mailbox-form-smtp-name').value,
|
||||
smtp_host: document.getElementById('mailbox-form-smtp-host').value,
|
||||
smtp_port: parseInt(document.getElementById('mailbox-form-smtp-port').value) || 587,
|
||||
smtp_user: document.getElementById('mailbox-form-smtp-user').value,
|
||||
smtp_password: document.getElementById('mailbox-form-smtp-pass').value,
|
||||
smtp_encryption: document.getElementById('mailbox-form-smtp-encryption').value,
|
||||
aktiivinen: true,
|
||||
};
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user