mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-03 16:02:02 +00:00
181 lines
10 KiB
Go
181 lines
10 KiB
Go
// Package mailboximport connects many mailboxes from one file: it reads any
|
|
// reasonable spreadsheet or pasted list, works out what each column means and
|
|
// where each domain's mail is hosted, and then connects the rows in the
|
|
// background, grouping whatever fails by cause.
|
|
package mailboximport
|
|
|
|
import (
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
)
|
|
|
|
// normalizeHeader folds a header to letters and digits, so "SMTP Host",
|
|
// "smtp_host" and "smtpHost" meet.
|
|
func normalizeHeader(h string) string {
|
|
var b strings.Builder
|
|
for _, r := range strings.ToLower(strings.TrimSpace(h)) {
|
|
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
|
b.WriteRune(r)
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// aliases maps a normalized header to a field. It covers our own template and
|
|
// the exports of the inbox vendors and sequencers people move mailboxes between.
|
|
var aliases = map[string]models.MailboxImportField{
|
|
"email": models.ImportFieldEmail, "emailaddress": models.ImportFieldEmail, "address": models.ImportFieldEmail,
|
|
"mailbox": models.ImportFieldEmail, "fromemail": models.ImportFieldEmail, "emailaccount": models.ImportFieldEmail,
|
|
"senderemail": models.ImportFieldEmail, "inbox": models.ImportFieldEmail, "account": models.ImportFieldEmail,
|
|
"mailboxemail": models.ImportFieldEmail, "emailid": models.ImportFieldEmail,
|
|
|
|
"name": models.ImportFieldName, "displayname": models.ImportFieldName, "fromname": models.ImportFieldName,
|
|
"sendername": models.ImportFieldName, "sendersname": models.ImportFieldName, "fullname": models.ImportFieldName,
|
|
"smtpfromname": models.ImportFieldName,
|
|
"firstname": models.ImportFieldFirstName, "first": models.ImportFieldFirstName, "givenname": models.ImportFieldFirstName,
|
|
"lastname": models.ImportFieldLastName, "last": models.ImportFieldLastName, "surname": models.ImportFieldLastName,
|
|
"familyname": models.ImportFieldLastName,
|
|
|
|
"password": models.ImportFieldPassword, "pass": models.ImportFieldPassword, "pwd": models.ImportFieldPassword,
|
|
"accountpassword": models.ImportFieldPassword, "mailboxpassword": models.ImportFieldPassword,
|
|
"emailpassword": models.ImportFieldPassword,
|
|
"apppassword": models.ImportFieldAppPassword, "applicationpassword": models.ImportFieldAppPassword,
|
|
"appspecificpassword": models.ImportFieldAppPassword, "googleapppassword": models.ImportFieldAppPassword,
|
|
"smtppassword": models.ImportFieldSMTPPassword, "smtppass": models.ImportFieldSMTPPassword,
|
|
"imappassword": models.ImportFieldIMAPPassword, "imappass": models.ImportFieldIMAPPassword,
|
|
|
|
"username": models.ImportFieldUsername, "user": models.ImportFieldUsername, "login": models.ImportFieldUsername,
|
|
"loginemail": models.ImportFieldUsername,
|
|
"smtpusername": models.ImportFieldSMTPUsername, "smtpuser": models.ImportFieldSMTPUsername,
|
|
"smtplogin": models.ImportFieldSMTPUsername, "smtpemail": models.ImportFieldSMTPUsername,
|
|
"imapusername": models.ImportFieldIMAPUsername, "imapuser": models.ImportFieldIMAPUsername,
|
|
"imaplogin": models.ImportFieldIMAPUsername, "imapemail": models.ImportFieldIMAPUsername,
|
|
|
|
"smtphost": models.ImportFieldSMTPHost, "smtpserver": models.ImportFieldSMTPHost, "outgoingserver": models.ImportFieldSMTPHost,
|
|
"smtpport": models.ImportFieldSMTPPort, "outgoingport": models.ImportFieldSMTPPort,
|
|
"smtpsecurity": models.ImportFieldSMTPSecurity, "smtpencryption": models.ImportFieldSMTPSecurity,
|
|
"smtpssl": models.ImportFieldSMTPSecurity, "smtpporttype": models.ImportFieldSMTPSecurity, "smtptls": models.ImportFieldSMTPSecurity,
|
|
"imaphost": models.ImportFieldIMAPHost, "imapserver": models.ImportFieldIMAPHost, "incomingserver": models.ImportFieldIMAPHost,
|
|
"imapport": models.ImportFieldIMAPPort, "incomingport": models.ImportFieldIMAPPort,
|
|
"imapsecurity": models.ImportFieldIMAPSecurity, "imapencryption": models.ImportFieldIMAPSecurity,
|
|
"imapssl": models.ImportFieldIMAPSecurity, "imapporttype": models.ImportFieldIMAPSecurity, "imaptls": models.ImportFieldIMAPSecurity,
|
|
|
|
"dailylimit": models.ImportFieldDailyLimit, "maxemailperday": models.ImportFieldDailyLimit,
|
|
"sendinglimit": models.ImportFieldDailyLimit, "dailysendinglimit": models.ImportFieldDailyLimit,
|
|
"dailyquota": models.ImportFieldDailyLimit, "campaignlimit": models.ImportFieldDailyLimit, "limit": models.ImportFieldDailyLimit,
|
|
"minwait": models.ImportFieldMinWait, "mingap": models.ImportFieldMinWait, "delay": models.ImportFieldMinWait,
|
|
"minwaittime": models.ImportFieldMinWait, "timegap": models.ImportFieldMinWait,
|
|
|
|
"warmup": models.ImportFieldWarmup, "warmupenabled": models.ImportFieldWarmup, "enablewarmup": models.ImportFieldWarmup,
|
|
"warmupstart": models.ImportFieldWarmupStart, "warmupbase": models.ImportFieldWarmupStart,
|
|
"warmupmax": models.ImportFieldWarmupMax, "warmuplimit": models.ImportFieldWarmupMax,
|
|
"totalwarmupperday": models.ImportFieldWarmupMax, "warmupdailygoal": models.ImportFieldWarmupMax,
|
|
"warmupincrease": models.ImportFieldWarmupIncrease, "warmupincrement": models.ImportFieldWarmupIncrease,
|
|
"dailyrampup": models.ImportFieldWarmupIncrease, "warmupdailyincrement": models.ImportFieldWarmupIncrease,
|
|
"rampup": models.ImportFieldWarmupIncrease,
|
|
"warmupreplyrate": models.ImportFieldWarmupReplyRate, "replyrate": models.ImportFieldWarmupReplyRate,
|
|
"replyratepercentage": models.ImportFieldWarmupReplyRate,
|
|
|
|
"replyto": models.ImportFieldReplyTo, "differentreplytoaddress": models.ImportFieldReplyTo,
|
|
"replytoaddress": models.ImportFieldReplyTo, "replytoemail": models.ImportFieldReplyTo,
|
|
"signature": models.ImportFieldSignature, "signaturehtml": models.ImportFieldSignature, "emailsignature": models.ImportFieldSignature,
|
|
"footer": models.ImportFieldSignature,
|
|
"tags": models.ImportFieldTags, "tag": models.ImportFieldTags, "labels": models.ImportFieldTags, "label": models.ImportFieldTags,
|
|
"timezone": models.ImportFieldTimezone, "tz": models.ImportFieldTimezone,
|
|
}
|
|
|
|
// secretFields never leave the server in a preview and never reach a download.
|
|
var secretFields = map[models.MailboxImportField]bool{
|
|
models.ImportFieldPassword: true,
|
|
models.ImportFieldAppPassword: true,
|
|
models.ImportFieldSMTPPassword: true,
|
|
models.ImportFieldIMAPPassword: true,
|
|
}
|
|
|
|
// allFields is every field a column can be mapped to, in the order the UI groups them.
|
|
var allFields = []models.MailboxImportField{
|
|
models.ImportFieldEmail, models.ImportFieldName, models.ImportFieldFirstName, models.ImportFieldLastName,
|
|
models.ImportFieldPassword, models.ImportFieldAppPassword, models.ImportFieldSMTPPassword, models.ImportFieldIMAPPassword,
|
|
models.ImportFieldUsername, models.ImportFieldSMTPUsername, models.ImportFieldIMAPUsername,
|
|
models.ImportFieldSMTPHost, models.ImportFieldSMTPPort, models.ImportFieldSMTPSecurity,
|
|
models.ImportFieldIMAPHost, models.ImportFieldIMAPPort, models.ImportFieldIMAPSecurity,
|
|
models.ImportFieldDailyLimit, models.ImportFieldMinWait,
|
|
models.ImportFieldWarmup, models.ImportFieldWarmupStart, models.ImportFieldWarmupMax,
|
|
models.ImportFieldWarmupIncrease, models.ImportFieldWarmupReplyRate,
|
|
models.ImportFieldReplyTo, models.ImportFieldSignature, models.ImportFieldTags, models.ImportFieldTimezone,
|
|
}
|
|
|
|
// fieldDescriptions are the choices offered to Jev for a column no rule placed.
|
|
var fieldDescriptions = map[models.MailboxImportField]string{
|
|
models.ImportFieldEmail: "the mailbox's email address",
|
|
models.ImportFieldName: "the sender's full display name",
|
|
models.ImportFieldFirstName: "the sender's first name",
|
|
models.ImportFieldLastName: "the sender's last name",
|
|
models.ImportFieldPassword: "the mailbox account's password",
|
|
models.ImportFieldAppPassword: "an app-specific password generated for mail clients",
|
|
models.ImportFieldSMTPPassword: "the password used for the outgoing (SMTP) server only",
|
|
models.ImportFieldIMAPPassword: "the password used for the incoming (IMAP) server only",
|
|
models.ImportFieldUsername: "the login name used to sign in to the mail servers",
|
|
models.ImportFieldSMTPUsername: "the login name for the outgoing (SMTP) server only",
|
|
models.ImportFieldIMAPUsername: "the login name for the incoming (IMAP) server only",
|
|
models.ImportFieldSMTPHost: "the outgoing (SMTP) server hostname",
|
|
models.ImportFieldSMTPPort: "the outgoing (SMTP) server port number",
|
|
models.ImportFieldSMTPSecurity: "the outgoing server's encryption (SSL, TLS or STARTTLS)",
|
|
models.ImportFieldIMAPHost: "the incoming (IMAP) server hostname",
|
|
models.ImportFieldIMAPPort: "the incoming (IMAP) server port number",
|
|
models.ImportFieldIMAPSecurity: "the incoming server's encryption (SSL, TLS or STARTTLS)",
|
|
models.ImportFieldDailyLimit: "how many campaign emails the mailbox may send per day",
|
|
models.ImportFieldMinWait: "the minimum wait between two sends from the mailbox",
|
|
models.ImportFieldWarmup: "whether warmup is switched on (yes/no)",
|
|
models.ImportFieldWarmupStart: "how many warmup emails to send on the first day",
|
|
models.ImportFieldWarmupMax: "the most warmup emails to send per day",
|
|
models.ImportFieldWarmupIncrease: "how many warmup emails to add each day",
|
|
models.ImportFieldWarmupReplyRate: "the percentage of warmup emails that get a reply",
|
|
models.ImportFieldReplyTo: "a reply-to email address different from the mailbox",
|
|
models.ImportFieldSignature: "the email signature",
|
|
models.ImportFieldTags: "tags or labels to put on the mailbox",
|
|
models.ImportFieldTimezone: "the mailbox's time zone",
|
|
models.ImportFieldIgnore: "something else that is not needed to connect or configure a mailbox",
|
|
}
|
|
|
|
func validField(f models.MailboxImportField) bool {
|
|
if f == models.ImportFieldIgnore {
|
|
return true
|
|
}
|
|
_, ok := fieldDescriptions[f]
|
|
return ok
|
|
}
|
|
|
|
// vendor recognizes a file by headers only its exporter writes.
|
|
type vendor struct {
|
|
id, label string
|
|
// all of these normalized headers must be present
|
|
needs []string
|
|
}
|
|
|
|
var vendors = []vendor{
|
|
{"zapmail", "Zapmail", []string{"apppassword", "recoveryemail"}},
|
|
{"inboxkit", "InboxKit", []string{"apppassword", "domainname"}},
|
|
{"smartlead", "Smartlead", []string{"fromname", "fromemail", "username", "maxemailperday"}},
|
|
{"instantly", "Instantly", []string{"imapusername", "smtpusername", "warmupenabled", "dailylimit"}},
|
|
{"saleshandy", "Saleshandy", []string{"emailaccount", "emailserviceprovider", "sendersname"}},
|
|
}
|
|
|
|
func detectVendor(normalized map[string]bool) *models.MailboxImportVendor {
|
|
for _, v := range vendors {
|
|
hit := true
|
|
for _, n := range v.needs {
|
|
if !normalized[n] {
|
|
hit = false
|
|
break
|
|
}
|
|
}
|
|
if hit {
|
|
return &models.MailboxImportVendor{ID: v.id, Label: v.label}
|
|
}
|
|
}
|
|
return nil
|
|
}
|