2025-03-12 15:05:46 +01:00
|
|
|
<script setup>
|
|
|
|
|
|
|
|
|
|
import { ref, useTemplateRef, computed } from 'vue';
|
|
|
|
|
import { Button, Dialog, FormGroup, SingleSelect, TextInput, NumberInput, Checkbox, PasswordInput } from 'pankow';
|
|
|
|
|
import SettingsItem from './SettingsItem.vue';
|
|
|
|
|
import MailModel from '../models/MailModel.js';
|
|
|
|
|
|
|
|
|
|
const props = defineProps(['domain']);
|
|
|
|
|
|
|
|
|
|
const mailModel = MailModel.create();
|
|
|
|
|
|
|
|
|
|
const providers = [
|
|
|
|
|
{ provider: 'cloudron-smtp', name: 'Built-in SMTP server' },
|
|
|
|
|
{ provider: 'external-smtp', name: 'External SMTP server', host: '', port: 587 },
|
|
|
|
|
{ provider: 'external-smtp-noauth', name: 'External SMTP server (No Authentication)', host: '', port: 587 },
|
|
|
|
|
{ provider: 'ses-smtp', name: 'Amazon SES', host: 'email-smtp.us-east-1.amazonaws.com', port: 587, spfDoc: 'https://docs.aws.amazon.com/ses/latest/DeveloperGuide/spf.html' },
|
|
|
|
|
{ provider: 'elasticemail-smtp', name: 'Elastic Email', host: 'smtp.elasticemail.com', port: 587, spfDoc: 'https://elasticemail.com/blog/marketing_tips/common-spf-errors' },
|
|
|
|
|
{ provider: 'google-smtp', name: 'Google', host: 'smtp.gmail.com', port: 587, spfDoc: 'https://support.google.com/a/answer/33786?hl=en' },
|
|
|
|
|
{ provider: 'mailgun-smtp', name: 'Mailgun', host: 'smtp.mailgun.org', port: 587, spfDoc: 'https://www.mailgun.com/blog/white-labeling-dns-records-your-customers-tips-tricks' },
|
|
|
|
|
{ provider: 'mailjet-smtp', name: 'Mailjet', host: '', port: 587, spfDoc: 'https://app.mailjet.com/docs/spf-dkim-guide' },
|
|
|
|
|
{ provider: 'office365-legacy-smtp', name: 'Office 365', host: 'smtp-legacy.office365.com', port: 587 }, // uses "login" AUTH instead of "plain"
|
|
|
|
|
{ provider: 'postmark-smtp', name: 'Postmark', host: 'smtp.postmarkapp.com', port: 587, spfDoc: 'https://postmarkapp.com/support/article/1092-how-do-i-set-up-spf-for-postmark' },
|
|
|
|
|
{ provider: 'sendgrid-smtp', name: 'SendGrid', host: 'smtp.sendgrid.net', port: 587, username: 'apikey', spfDoc: 'https://sendgrid.com/docs/ui/account-and-settings/spf-records/' },
|
|
|
|
|
{ provider: 'sparkpost-smtp', name: 'SparkPost', host: 'smtp.sparkpostmail.com', port: 587, username: 'SMTP_Injection', spfDoc: 'https://www.sparkpost.com/resources/email-explained/spf-sender-policy-framework/' },
|
|
|
|
|
{ provider: 'noop', name: 'Disable' },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const providerSpfDoc = computed(() => {
|
|
|
|
|
const tmp = providers.find(p => p.provider === provider.value);
|
|
|
|
|
return (tmp ? tmp.spfDoc : '') || '';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mailConfig = ref({});
|
|
|
|
|
const dialog = useTemplateRef('dialog');
|
|
|
|
|
const busy = ref(false);
|
|
|
|
|
const formError = ref('');
|
|
|
|
|
const adminDomain = ref('');
|
|
|
|
|
const provider = ref('cloudron-smtp');
|
|
|
|
|
const host = ref('');
|
|
|
|
|
const port = ref(1);
|
|
|
|
|
const acceptSelfSignedCerts = ref(true);
|
|
|
|
|
const serverApiToken = ref('');
|
|
|
|
|
const username = ref('');
|
|
|
|
|
const password = ref('');
|
|
|
|
|
|
|
|
|
|
function usesExternalServer(provider) {
|
|
|
|
|
return provider !== 'cloudron-smtp' && provider !== 'noop';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function usesTokenAuth(provider) {
|
|
|
|
|
return provider === 'postmark-smtp' || provider === 'sendgrid-smtp' || provider === 'sparkpost-smtp';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function usesPasswordAuth(provider) {
|
|
|
|
|
return provider === 'external-smtp'
|
|
|
|
|
|| provider === 'ses-smtp'
|
|
|
|
|
|| provider === 'google-smtp'
|
|
|
|
|
|| provider === 'mailgun-smtp'
|
|
|
|
|
|| provider === 'elasticemail-smtp'
|
|
|
|
|
|| provider === 'office365-legacy-smtp'
|
|
|
|
|
|| provider === 'mailjet-smtp';
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-26 19:31:54 +01:00
|
|
|
function onProviderChange() {
|
|
|
|
|
const tmp = providers.find(p => p.provider === provider.value);
|
|
|
|
|
if (!tmp) return;
|
|
|
|
|
|
|
|
|
|
if (tmp.host) host.value = tmp.host;
|
|
|
|
|
if (tmp.port) port.value = tmp.port;
|
|
|
|
|
if (tmp.username) username.value = tmp.username;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-12 15:05:46 +01:00
|
|
|
async function onShowDialog() {
|
|
|
|
|
formError.value = '';
|
|
|
|
|
busy.value = false;
|
|
|
|
|
|
|
|
|
|
const [error, result] = await mailModel.config(props.domain);
|
|
|
|
|
if (error) return console.error(error);
|
|
|
|
|
|
|
|
|
|
mailConfig.value = result;
|
|
|
|
|
provider.value = result.relay.provider;
|
|
|
|
|
host.value = result.relay.host;
|
|
|
|
|
port.value = result.relay.port;
|
|
|
|
|
username.value = result.relay.username;
|
|
|
|
|
password.value = result.relay.password;
|
|
|
|
|
acceptSelfSignedCerts.value = result.relay.acceptSelfSignedCerts;
|
|
|
|
|
serverApiToken.value = result.relay.serverApiToken;
|
|
|
|
|
|
|
|
|
|
dialog.value.open();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onSubmit() {
|
|
|
|
|
busy.value = true;
|
|
|
|
|
formError.value = '';
|
|
|
|
|
|
|
|
|
|
const data = {
|
|
|
|
|
provider: provider.value,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (usesExternalServer(data.provider)) {
|
|
|
|
|
data.username = username.value;
|
|
|
|
|
data.password = password.value;
|
|
|
|
|
data.host = host.value;
|
|
|
|
|
data.port = parseInt(port.value);
|
|
|
|
|
data.acceptSelfSignedCerts = acceptSelfSignedCerts.value;
|
|
|
|
|
data.forceFromAddress = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fill in provider specific username/password usage
|
|
|
|
|
if (data.provider === 'postmark-smtp') {
|
|
|
|
|
data.username = serverApiToken.value;
|
|
|
|
|
data.password = serverApiToken.value;
|
|
|
|
|
data.forceFromAddress = true; // postmark requires the "From:" in mail to be a Sender Signature
|
|
|
|
|
} else if (data.provider === 'sendgrid-smtp') {
|
|
|
|
|
data.username = 'apikey';
|
|
|
|
|
data.password = serverApiToken.value;
|
|
|
|
|
} else if (data.provider === 'sparkpost-smtp') {
|
|
|
|
|
data.username = 'SMTP_Injection';
|
|
|
|
|
data.password = serverApiToken.value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [error] = await mailModel.setMailRelay(props.domain, data);
|
|
|
|
|
if (error) {
|
|
|
|
|
formError.value = error.body ? error.body.message : 'Internal error';
|
|
|
|
|
busy.value = false;
|
|
|
|
|
return console.error(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dialog.value.close();
|
|
|
|
|
|
|
|
|
|
busy.value = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div>
|
|
|
|
|
<Dialog ref="dialog"
|
|
|
|
|
:title="$t('email.outbound.title')"
|
|
|
|
|
:confirm-label="$t('main.dialog.save')"
|
|
|
|
|
:confirm-busy="busy"
|
|
|
|
|
:reject-label="busy ? '' : $t('main.dialog.cancel')"
|
|
|
|
|
reject-style="secondary"
|
|
|
|
|
@confirm="onSubmit()"
|
|
|
|
|
>
|
|
|
|
|
<div>
|
2025-03-26 19:31:54 +01:00
|
|
|
<SingleSelect v-model="provider" :options="providers" option-key="provider" option-label="name" @select="onProviderChange()"/>
|
2025-03-12 15:05:46 +01:00
|
|
|
|
|
|
|
|
<div class="text-danger" style="margin: 10px 0;" v-if="provider === 'noop'">{{ $t(domain === adminDomain ? 'email.outbound.noopAdminDomainWarning' : 'email.outbound.noopNonAdminDomainWarning') }}</div>
|
|
|
|
|
|
|
|
|
|
<div class="text-danger" v-if="formError">{{ formError }}</div>
|
|
|
|
|
|
|
|
|
|
<form @submit.prevent="onSubmit()" autocomplete="off" v-if="usesExternalServer(provider)">
|
|
|
|
|
<fieldset :disabled="busy">
|
|
|
|
|
<input type="submit" style="display: none"/>
|
|
|
|
|
|
|
|
|
|
<FormGroup>
|
|
|
|
|
<label for="hostInput">{{ $t('email.outbound.mailRelay.host') }}</label>
|
|
|
|
|
<TextInput id="hostInput" v-model="host" required />
|
|
|
|
|
</FormGroup>
|
|
|
|
|
|
|
|
|
|
<FormGroup>
|
|
|
|
|
<label for="portInput">{{ $t('email.outbound.mailRelay.port') }}</label>
|
|
|
|
|
<NumberInput id="portInput" v-model="port" min="1" required />
|
|
|
|
|
</FormGroup>
|
|
|
|
|
|
|
|
|
|
<Checkbox v-model="acceptSelfSignedCerts" :label="$t('email.outbound.mailRelay.selfsignedCheckbox')" v-if="provider === 'external-smtp' || provider === 'external-smtp-noauth'"/>
|
|
|
|
|
|
|
|
|
|
<!-- Postmark, Sendgrid, SparkPost -->
|
|
|
|
|
<FormGroup v-if="usesTokenAuth(provider)">
|
|
|
|
|
<label for="serverApiTokenInput">{{ $t('email.outbound.mailRelay.apiTokenOrKey') }}</label>
|
|
|
|
|
<TextInput id="serverApiTokenInput" v-model="serverApiToken" required />
|
|
|
|
|
</FormGroup>
|
|
|
|
|
|
|
|
|
|
<!-- Other -->
|
|
|
|
|
<FormGroup v-if="usesPasswordAuth(provider)">
|
|
|
|
|
<label for="usernameInput">{{ $t('email.outbound.mailRelay.username') }}</label>
|
|
|
|
|
<TextInput id="usernameInput" v-model="username" required />
|
|
|
|
|
</FormGroup>
|
|
|
|
|
|
|
|
|
|
<FormGroup v-if="usesPasswordAuth(provider)">
|
|
|
|
|
<label for="passwordInput">{{ $t('email.outbound.mailRelay.password') }}</label>
|
|
|
|
|
<PasswordInput id="passwordInput" v-model="password" required />
|
|
|
|
|
</FormGroup>
|
|
|
|
|
</fieldset>
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<div v-if="providerSpfDoc" class="text-danger" style="margin: 10px 0; " v-html="$t('email.outbound.mailRelay.spfDocInfo', { name: provider, spfDocsLink: providerSpfDoc })"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
|
|
|
|
<SettingsItem>
|
|
|
|
|
<FormGroup>
|
|
|
|
|
<label>{{ $t('email.outbound.title') }} <sup><a href="https://docs.cloudron.io/email/#relay-outbound-mails" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
|
|
|
|
<div v-html="$t('email.outbound.description')"></div>
|
|
|
|
|
</FormGroup>
|
|
|
|
|
<div style="display: flex; align-items: center">
|
2025-03-28 17:51:39 +01:00
|
|
|
<Button tool plain @click="onShowDialog()">{{ $t('main.dialog.edit') }}</Button>
|
2025-03-12 15:05:46 +01:00
|
|
|
</div>
|
|
|
|
|
</SettingsItem>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|