fixed sending of email(sending email considered it as spam)

This commit is contained in:
ghostersk
2026-03-07 15:20:49 +00:00
parent 1cf003edc4
commit b118056176
7 changed files with 129 additions and 18 deletions

View File

@@ -104,6 +104,7 @@ func main() {
api.HandleFunc("/accounts", h.API.ListAccounts).Methods("GET")
api.HandleFunc("/accounts", h.API.AddAccount).Methods("POST")
api.HandleFunc("/accounts/test", h.API.TestConnection).Methods("POST")
api.HandleFunc("/accounts/detect", h.API.DetectMailSettings).Methods("POST")
api.HandleFunc("/accounts/{id:[0-9]+}", h.API.GetAccount).Methods("GET")
api.HandleFunc("/accounts/{id:[0-9]+}", h.API.UpdateAccount).Methods("PUT")
api.HandleFunc("/accounts/{id:[0-9]+}", h.API.DeleteAccount).Methods("DELETE")

View File

@@ -446,6 +446,7 @@ func (d *DB) ListAuditLogs(page, pageSize int, eventFilter string) (*models.Audi
}, rows.Err()
}
// ---- Email Accounts ----
func (d *DB) CreateAccount(a *models.EmailAccount) error {
@@ -716,8 +717,7 @@ func (d *DB) GetFolderByPath(accountID int64, fullPath string) (*models.Folder,
COALESCE(is_hidden,0), COALESCE(sync_enabled,1)
FROM folders WHERE account_id=? AND full_path=?`, accountID, fullPath,
).Scan(&f.ID, &f.AccountID, &f.Name, &f.FullPath, &f.FolderType, &f.UnreadCount, &f.TotalCount, &isHidden, &syncEnabled)
f.IsHidden = isHidden == 1
f.SyncEnabled = syncEnabled == 1
f.IsHidden = isHidden == 1; f.SyncEnabled = syncEnabled == 1
if err == sql.ErrNoRows {
return nil, nil
}
@@ -1072,12 +1072,8 @@ func (d *DB) IsRemoteContentAllowed(userID int64, sender string) (bool, error) {
// SetFolderVisibility sets is_hidden and sync_enabled for a folder owned by the user.
func (d *DB) SetFolderVisibility(folderID, userID int64, isHidden, syncEnabled bool) error {
ih, se := 0, 0
if isHidden {
ih = 1
}
if syncEnabled {
se = 1
}
if isHidden { ih = 1 }
if syncEnabled { se = 1 }
_, err := d.sql.Exec(`
UPDATE folders SET is_hidden=?, sync_enabled=?
WHERE id=? AND account_id IN (SELECT id FROM email_accounts WHERE user_id=?)`,
@@ -1089,11 +1085,11 @@ func (d *DB) GetFolderByID(folderID int64) (*models.Folder, error) {
f := &models.Folder{}
var isHidden, syncEnabled int
err := d.sql.QueryRow(
`SELECT id, account_id, name, full_path, folder_type, unread_count, total_count
`SELECT id, account_id, name, full_path, folder_type, unread_count, total_count,
COALESCE(is_hidden,0), COALESCE(sync_enabled,1)
FROM folders WHERE id=?`, folderID,
).Scan(&f.ID, &f.AccountID, &f.Name, &f.FullPath, &f.FolderType, &f.UnreadCount, &f.TotalCount, &isHidden, &syncEnabled)
f.IsHidden = isHidden == 1
f.SyncEnabled = syncEnabled == 1
f.IsHidden = isHidden == 1; f.SyncEnabled = syncEnabled == 1
if err == sql.ErrNoRows {
return nil, nil
}

View File

@@ -639,7 +639,12 @@ func SendMessageFull(ctx context.Context, account *gomailModels.EmailAccount, re
func buildMIMEMessage(buf *bytes.Buffer, account *gomailModels.EmailAccount, req *gomailModels.ComposeRequest) string {
from := netmail.Address{Name: account.DisplayName, Address: account.EmailAddress}
boundary := fmt.Sprintf("gomail_%x", time.Now().UnixNano())
msgID := fmt.Sprintf("<%d.%s@gomail>", time.Now().UnixNano(), strings.ReplaceAll(account.EmailAddress, "@", "."))
// Use the sender's actual domain for Message-ID so it passes spam filters
domain := account.EmailAddress
if at := strings.Index(domain, "@"); at >= 0 {
domain = domain[at+1:]
}
msgID := fmt.Sprintf("<%d.%s@%s>", time.Now().UnixNano(), strings.ReplaceAll(account.EmailAddress, "@", "."), domain)
buf.WriteString("Message-ID: " + msgID + "\r\n")
buf.WriteString("From: " + from.String() + "\r\n")

View File

@@ -5,9 +5,11 @@ import (
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/yourusername/gomail/config"
@@ -240,6 +242,83 @@ func (h *APIHandler) TestConnection(w http.ResponseWriter, r *http.Request) {
h.writeJSON(w, map[string]bool{"ok": true})
}
// DetectMailSettings tries common IMAP/SMTP combinations for a domain and returns
// the first working combination, or sensible defaults if nothing connects.
func (h *APIHandler) DetectMailSettings(w http.ResponseWriter, r *http.Request) {
var req struct {
Email string `json:"email"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Email == "" {
h.writeError(w, http.StatusBadRequest, "email required")
return
}
at := strings.Index(req.Email, "@")
if at < 0 {
h.writeError(w, http.StatusBadRequest, "invalid email")
return
}
domain := req.Email[at+1:]
type candidate struct {
host string
port int
}
imapCandidates := []candidate{
{"imap." + domain, 993},
{"mail." + domain, 993},
{"imap." + domain, 143},
{"mail." + domain, 143},
}
smtpCandidates := []candidate{
{"smtp." + domain, 587},
{"mail." + domain, 587},
{"smtp." + domain, 465},
{"mail." + domain, 465},
{"smtp." + domain, 25},
}
type result struct {
IMAPHost string `json:"imap_host"`
IMAPPort int `json:"imap_port"`
SMTPHost string `json:"smtp_host"`
SMTPPort int `json:"smtp_port"`
Detected bool `json:"detected"`
}
res := result{
IMAPHost: "imap." + domain,
IMAPPort: 993,
SMTPHost: "smtp." + domain,
SMTPPort: 587,
}
// Try IMAP candidates (TCP dial only, no auth needed to detect)
for _, c := range imapCandidates {
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", c.host, c.port), 4*time.Second)
if err == nil {
conn.Close()
res.IMAPHost = c.host
res.IMAPPort = c.port
res.Detected = true
break
}
}
// Try SMTP candidates
for _, c := range smtpCandidates {
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", c.host, c.port), 4*time.Second)
if err == nil {
conn.Close()
res.SMTPHost = c.host
res.SMTPPort = c.port
res.Detected = true
break
}
}
h.writeJSON(w, res)
}
func (h *APIHandler) DeleteAccount(w http.ResponseWriter, r *http.Request) {
userID := middleware.GetUserID(r)
accountID := pathInt64(r, "id")

View File

@@ -425,4 +425,4 @@ body.admin-page{overflow:auto;background:var(--bg)}
/* ── Icon sync button ────────────────────────────────────────── */
.icon-sync-btn{background:none;border:none;color:var(--muted);cursor:pointer;
padding:2px;border-radius:4px;line-height:1;flex-shrink:0;transition:color .15s}
.icon-sync-btn:hover{color:var(--text)}
.icon-sync-btn:hover{color:var(--text)}

View File

@@ -139,8 +139,29 @@ async function addIMAPAccount() {
btn.disabled=true;btn.textContent='Connecting...';
const r=await api('POST','/accounts',body);
btn.disabled=false;btn.textContent='Connect';
if (r?.ok){toast('Account added!','success');closeModal('add-account-modal');loadAccounts();loadFolders();loadMessages();}
else toast(r?.error||'Failed to add account','error');
if (r?.ok){
toast('Account added — syncing…','success');
closeModal('add-account-modal');
await loadAccounts();
// Background sync takes a moment — reload folders/messages after a short wait
setTimeout(async ()=>{ await loadFolders(); await loadMessages(); toast('Sync complete','success'); }, 3000);
} else toast(r?.error||'Failed to add account','error');
}
async function detectMailSettings() {
const email=document.getElementById('imap-email').value.trim();
if (!email||!email.includes('@')){toast('Enter your email address first','error');return;}
const btn=document.getElementById('detect-btn');
btn.innerHTML='<span class="spinner-inline"></span>Detecting…';btn.disabled=true;
const r=await api('POST','/accounts/detect',{email});
btn.textContent='Auto-detect';btn.disabled=false;
if (!r){toast('Detection failed','error');return;}
document.getElementById('imap-host').value=r.imap_host||'';
document.getElementById('imap-port').value=r.imap_port||993;
document.getElementById('smtp-host').value=r.smtp_host||'';
document.getElementById('smtp-port').value=r.smtp_port||587;
if(r.detected) toast(`Detected ${r.imap_host} / ${r.smtp_host}`,'success');
else toast('No servers found — filled with defaults based on domain','info');
}
async function syncNow(id, e) {
@@ -885,4 +906,4 @@ document.addEventListener('DOMContentLoaded', ()=>{
initTagField('compose-cc-tags');
initTagField('compose-bcc-tags');
init();
});
});

View File

@@ -170,9 +170,18 @@
</button>
</div>
<div class="modal-divider"><span>or add IMAP account</span></div>
<div class="modal-field"><label>Email Address</label><input type="email" id="imap-email" placeholder="you@example.com"></div>
<div class="modal-field"><label>Email Address</label>
<div style="display:flex;gap:8px;flex:1">
<input type="email" id="imap-email" placeholder="you@example.com" style="flex:1">
<button class="btn-secondary" id="detect-btn" onclick="detectMailSettings()" style="white-space:nowrap;font-size:12px">Auto-detect</button>
</div>
</div>
<div class="modal-field"><label>Display Name</label><input type="text" id="imap-name" placeholder="Your Name"></div>
<div class="modal-field"><label>Password / App Password</label><input type="password" id="imap-password"></div>
<div style="font-size:11px;color:var(--muted);padding:0 0 8px;line-height:1.6">
Common ports — IMAP: <strong>993</strong> TLS/SSL, <strong>143</strong> STARTTLS/Plain &nbsp;·&nbsp;
SMTP: <strong>587</strong> STARTTLS, <strong>465</strong> TLS/SSL, <strong>25</strong> Plain
</div>
<div class="modal-row">
<div class="modal-field"><label>IMAP Host</label><input type="text" id="imap-host" placeholder="imap.example.com"></div>
<div class="modal-field"><label>IMAP Port</label><input type="number" id="imap-port" value="993"></div>
@@ -275,4 +284,4 @@
{{define "scripts"}}
<script src="/static/js/app.js"></script>
{{end}}
{{end}}