Compare commits
3 Commits
8730b7c199
...
0d41b2b813
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d41b2b813 | |||
| 7183b21d24 | |||
| dbde3a5834 |
@ -10,6 +10,8 @@ export class Field {
|
||||
categoryName = null;
|
||||
isHidden = false;
|
||||
isDisabled = false;
|
||||
isRequired = false;
|
||||
allowMultiple = false;
|
||||
|
||||
constructor(key) {
|
||||
this.key = key;
|
||||
@ -31,6 +33,11 @@ export class Field {
|
||||
|
||||
// Builder methods
|
||||
|
||||
required() {
|
||||
this.isRequired = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
label(label) {
|
||||
this.displayName = label;
|
||||
return this;
|
||||
@ -55,4 +62,9 @@ export class Field {
|
||||
this.isDisabled = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
multiple() {
|
||||
this.allowMultiple = true;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,29 +1,66 @@
|
||||
import { FieldInputItem } from "./FieldInputItem.js";
|
||||
|
||||
/**
|
||||
* Represents the actual input element on the creator tool
|
||||
* A field may have multiple inputs if it allows multiple values
|
||||
* May contain multiple items if the field allows multiple values
|
||||
*/
|
||||
export class FieldInput {
|
||||
constructor(field, parentElem) {
|
||||
this.field = field;
|
||||
this.id = "field-" + field.key;
|
||||
this.items = [];
|
||||
this.parent = parentElem;
|
||||
|
||||
if (!field.isHidden)
|
||||
parentElem.innerHTML += `
|
||||
<label for="${field.key}">${field.displayName}</label>
|
||||
<p class="description">${field.description ?? ""}</p>
|
||||
${field.getInputHtml(this.id)}
|
||||
`;
|
||||
this.appendHtml(parentElem);
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return this.field.isValidInput(this.id);
|
||||
appendHtml(parent) {
|
||||
const fieldContainer = document.createElement("div");
|
||||
fieldContainer.className = "field-container";
|
||||
|
||||
const item = new FieldInputItem(this, fieldContainer);
|
||||
|
||||
if (!this.field.isHidden) {
|
||||
parent.insertAdjacentHTML("beforeend", `
|
||||
<label for="${this.field.key}">${this.field.displayName}</label>
|
||||
<p class="description">${this.field.description ?? ""}</p>
|
||||
`);
|
||||
|
||||
parent.appendChild(fieldContainer);
|
||||
|
||||
if (this.field.allowMultiple && this.items.length === 0) {
|
||||
const addBtn = document.createElement("button");
|
||||
addBtn.className = "text-button";
|
||||
addBtn.type = "button";
|
||||
addBtn.innerText = "+ Add";
|
||||
addBtn.onclick = () => this.addItem(fieldContainer);
|
||||
parent.appendChild(addBtn);
|
||||
}
|
||||
}
|
||||
|
||||
this.items.push(item);
|
||||
this.updateItems();
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.field.getInputValue(this.id);
|
||||
updateItems() {
|
||||
for (const item of this.items) item.update();
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.field.inputToString(this.id);
|
||||
addItem(parent) {
|
||||
const item = new FieldInputItem(this, parent);
|
||||
this.items.push(item);
|
||||
|
||||
this.updateItems();
|
||||
}
|
||||
|
||||
removeItem(id) {
|
||||
const i = this.items.findIndex(item => item.id === id);
|
||||
|
||||
this.items[i].remove();
|
||||
this.items.splice(i, 1);
|
||||
|
||||
this.updateItems();
|
||||
|
||||
this.parent.closest("form").onchange();
|
||||
}
|
||||
}
|
||||
|
||||
59
assets/scripts/FieldInputItem.js
Normal file
59
assets/scripts/FieldInputItem.js
Normal file
@ -0,0 +1,59 @@
|
||||
export class FieldInputItem {
|
||||
container = null;
|
||||
removeBtn = null;
|
||||
|
||||
constructor(input, parentElem) {
|
||||
this.input = input;
|
||||
this.field = input.field;
|
||||
this.id = input.id + (input.field.allowMultiple ? "-" + Math.random().toString().slice(2, 5) : "");
|
||||
|
||||
this.appendHtml(parentElem);
|
||||
}
|
||||
|
||||
appendHtml(parent) {
|
||||
const container = document.createElement("div");
|
||||
container.className = "input-container";
|
||||
|
||||
if (this.field.allowMultiple) {
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.innerHTML = this.field.getInputHtml(this.id);
|
||||
container.appendChild(wrapper);
|
||||
|
||||
const removeBtn = document.createElement("button");
|
||||
removeBtn.type = "button";
|
||||
removeBtn.className = "text-button";
|
||||
removeBtn.innerHTML = "× Remove";
|
||||
removeBtn.onclick = () => this.input.removeItem(this.id);
|
||||
container.appendChild(removeBtn);
|
||||
|
||||
this.removeBtn = removeBtn;
|
||||
} else {
|
||||
container.innerHTML = this.field.getInputHtml(this.id);
|
||||
}
|
||||
|
||||
parent.appendChild(container);
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.removeBtn) {
|
||||
this.removeBtn.style.display = this.input.items.length > 1 ? "inline-block" : "none";
|
||||
}
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.container.remove();
|
||||
}
|
||||
|
||||
isValid() {
|
||||
return this.field.isValidInput(this.id);
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.field.getInputValue(this.id);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.field.inputToString(this.id);
|
||||
}
|
||||
}
|
||||
55
assets/scripts/records/DkimRecord.js
Normal file
55
assets/scripts/records/DkimRecord.js
Normal file
@ -0,0 +1,55 @@
|
||||
import { TagListRecord } from "./TagListRecord.js";
|
||||
import { ConstantTag } from "../tags/ConstantTag.js";
|
||||
import { EnumTag } from "../tags/EnumTag.js";
|
||||
import { TextTag } from "../tags/TextTag.js";
|
||||
import { FlagsTag } from "../tags/FlagsTag.js";
|
||||
|
||||
export class DkimRecord extends TagListRecord {
|
||||
static fields = [
|
||||
new ConstantTag("v", "DKIM1")
|
||||
.required()
|
||||
.hidden()
|
||||
.pos(0),
|
||||
|
||||
new TextTag("p")
|
||||
.label("Public key")
|
||||
.desc("Base64-encoded public key data")
|
||||
.required()
|
||||
.pos(1),
|
||||
|
||||
new FlagsTag("t", ["y", "s"])
|
||||
.label("Flags")
|
||||
.desc("Optional extra options that can be enabled")
|
||||
.options(["Test mode", "Require identical domain in i= and d= tags of DKIM signature (Recommended)"])
|
||||
.default("")
|
||||
.pos(1),
|
||||
|
||||
new EnumTag("h", ["sha1", "sha256"])
|
||||
.label("Hash algorithms")
|
||||
.desc("Which hash algorithms are allowed to be used. If not set, all are allowed")
|
||||
.options(["SHA-1", "SHA-256"])
|
||||
.category("advanced")
|
||||
.pos(1),
|
||||
|
||||
new TextTag("n")
|
||||
.label("Note")
|
||||
.desc("Any extra comments for humans. Not parsed by machines")
|
||||
.category("advanced")
|
||||
.default("")
|
||||
.pos(1),
|
||||
|
||||
new EnumTag("k", ["rsa"])
|
||||
.disabled()
|
||||
.default("rsa")
|
||||
.pos(1),
|
||||
|
||||
new EnumTag("s", ["*", "email"])
|
||||
.disabled()
|
||||
.default("*")
|
||||
.pos(1),
|
||||
];
|
||||
|
||||
static categories = {
|
||||
"advanced": "Advanced",
|
||||
};
|
||||
}
|
||||
@ -78,9 +78,10 @@ export class DmarcRecord extends TagListRecord {
|
||||
.default(86400)
|
||||
.pos(2),
|
||||
|
||||
new Tag("rf")
|
||||
new EnumTag("rf", ["afrf"])
|
||||
.disabled()
|
||||
.default("afrf"), // Other values not supported
|
||||
.default("afrf")
|
||||
.pos(2),
|
||||
];
|
||||
|
||||
static categories = {
|
||||
|
||||
@ -86,10 +86,10 @@ export class SpfRecord {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
static createFromFieldInputs(fieldInputs) {
|
||||
const tokens = fieldInputs
|
||||
.filter(input => !input.field.isDisabled && input.isValid())
|
||||
.map(input => input.toString());
|
||||
static createFromFieldInputItems(items) {
|
||||
const tokens = items
|
||||
.filter(item => !item.field.isDisabled && item.isValid())
|
||||
.map(item => item.toString());
|
||||
|
||||
const text = tokens.join(" ");
|
||||
|
||||
@ -108,11 +108,19 @@ export class SpfRecord {
|
||||
|
||||
const term = this.constructor.fields.find(f => f.key === name);
|
||||
if (!term) {
|
||||
throw new ValidationError(`Unknown term: ${name}`);
|
||||
throw new ValidationError(name ? `Unknown term: ${name}` : "Syntax error");
|
||||
}
|
||||
|
||||
const [directive, value] = token.split(term.separator);
|
||||
const [, qualifier, key] = directive.match(/^([-+?~]?)(\w*)/);
|
||||
const [, qualifier, key] = directive.match(/^([-+?~]?)(.*)/);
|
||||
|
||||
if (key !== name) {
|
||||
throw new ValidationError(`Invalid separator for term: ${name}`);
|
||||
}
|
||||
|
||||
if (token.includes(term.separator) && !value) {
|
||||
throw new ValidationError(`No value specified for term: ${name}`);
|
||||
}
|
||||
|
||||
result.push({ qualifier, key, value });
|
||||
}
|
||||
@ -135,7 +143,7 @@ export class SpfRecord {
|
||||
const term = this.constructor.fields.find(d => d.key === input.key);
|
||||
|
||||
if (!term) {
|
||||
throw new ValidationError(`Unknown term: ${input.key}`);
|
||||
throw new ValidationError(input.key ? `Unknown term: ${input.key}` : "Syntax error");
|
||||
}
|
||||
|
||||
if (term.position < lastPos) {
|
||||
@ -144,8 +152,8 @@ export class SpfRecord {
|
||||
throw new ValidationError(`Term "${lastDirective.key}" must come after "${term.key}"`);
|
||||
}
|
||||
|
||||
if (term instanceof Modifier && input.qualifier) {
|
||||
throw new ValidationError(`Modifier "${term.key}" must not have a qualifier`)
|
||||
if (!term.qualifierAllowed && input.qualifier) {
|
||||
throw new ValidationError(`Term "${term.key}" must not have a qualifier`)
|
||||
}
|
||||
|
||||
if (!input.value && term.valueRequirement === ValueRequirement.REQUIRED) {
|
||||
|
||||
@ -8,10 +8,10 @@ export class TagListRecord {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
static createFromFieldInputs(fieldInputs) {
|
||||
const tokens = fieldInputs
|
||||
.filter(input => !input.field.isDisabled && input.isValid())
|
||||
.filter(input => !input.field.defaultValue || input.getValue() !== input.field.defaultValue)
|
||||
static createFromFieldInputItems(items) {
|
||||
const tokens = items
|
||||
.filter(item => !item.field.isDisabled && item.isValid())
|
||||
.filter(item => item.field.defaultValue === null || item.getValue() !== item.field.defaultValue)
|
||||
.map(input => input.toString());
|
||||
|
||||
const text = tokens.join("; ");
|
||||
|
||||
@ -4,6 +4,7 @@ import { ValueRequirement } from "./ValueRequirement.js";
|
||||
export class Mechanism extends Term {
|
||||
separator = ":";
|
||||
placeholder = null;
|
||||
qualifierAllowed = true;
|
||||
|
||||
constructor(key) {
|
||||
super(key);
|
||||
|
||||
@ -4,6 +4,7 @@ import { validateSpfDomain } from "./utils.js";
|
||||
|
||||
export class Modifier extends Term {
|
||||
separator = "=";
|
||||
qualifierAllowed = false;
|
||||
|
||||
constructor(key) {
|
||||
super(key);
|
||||
|
||||
@ -3,9 +3,7 @@ import { Field } from "../Field.js";
|
||||
|
||||
export class Term extends Field {
|
||||
separator = null;
|
||||
isRequired = false;
|
||||
position = null;
|
||||
allowMultiple = false;
|
||||
valueRequirement = ValueRequirement.REQUIRED;
|
||||
|
||||
constructor(key) {
|
||||
@ -42,21 +40,11 @@ export class Term extends Field {
|
||||
|
||||
// Builder methods
|
||||
|
||||
required() {
|
||||
this.isRequired = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
pos(i) {
|
||||
this.position = i;
|
||||
return this;
|
||||
}
|
||||
|
||||
multiple() {
|
||||
this.allowMultiple = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
value(requirement) {
|
||||
this.valueRequirement = requirement;
|
||||
return this;
|
||||
|
||||
@ -3,6 +3,7 @@ import { ValidationError } from "../ValidationError.js";
|
||||
|
||||
export class VersionTerm extends Term {
|
||||
separator = "=";
|
||||
qualifierAllowed = false;
|
||||
|
||||
constructor(key, version) {
|
||||
super(key);
|
||||
|
||||
@ -17,9 +17,9 @@ export class EnumTag extends Tag {
|
||||
|
||||
getInputHtml(id) {
|
||||
return `<select id="${id}" name="${this.key}" ${this.isRequired ? "required" : ""}>` +
|
||||
(this.isRequired || this.defaultValue ? "" : `<option value="" selected><not set></option>`) +
|
||||
(this.isRequired || this.initialValue ? "" : `<option value="" selected><not set></option>`) +
|
||||
this.values.map((value, i) =>
|
||||
`<option value="${value}" ${this.defaultValue === value ? "selected" : ""}>
|
||||
`<option value="${value}" ${this.initialValue === value ? "selected" : ""}>
|
||||
${this.optionLabels[i] + (this.defaultValue === value ? " (Default)" : "")}
|
||||
</option>`
|
||||
) +
|
||||
|
||||
28
assets/scripts/tags/FlagsTag.js
Normal file
28
assets/scripts/tags/FlagsTag.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { Tag } from "./Tag.js";
|
||||
|
||||
export class FlagsTag extends Tag {
|
||||
constructor(key, flags) {
|
||||
super(key);
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
options(labels) {
|
||||
this.flagLabels = labels;
|
||||
return this;
|
||||
}
|
||||
|
||||
getInputHtml() {
|
||||
return `
|
||||
<div style="display: flex; flex-direction: column; gap: 0.5rem">` +
|
||||
this.flags.map((flag, i) => `
|
||||
<label><input id="${this.id + "-" + flag}" type="checkbox"> ${this.flagLabels[i]}</label>
|
||||
`).join("") +
|
||||
"</div>";
|
||||
}
|
||||
|
||||
getInputValue() {
|
||||
return this.flags.map(flag => document.getElementById(this.id + "-" + flag).checked ? flag : "")
|
||||
.filter(val => val)
|
||||
.join(":");
|
||||
}
|
||||
}
|
||||
@ -23,7 +23,7 @@ export class IntTag extends Tag {
|
||||
}
|
||||
|
||||
getInputHtml(id) {
|
||||
return `<input id="${id}" type="number" name="${this.key}" min="${this.min}" max="${this.max}" ${this.isRequired ? "required" : ""} placeholder="${this.defaultValue ?? ""}">`;
|
||||
return `<input id="${id}" type="number" name="${this.key}" min="${this.min}" max="${this.max}" ${this.isRequired ? "required" : ""} placeholder="${this.initialValue ?? ""}">`;
|
||||
}
|
||||
|
||||
getInputValue(id) {
|
||||
|
||||
@ -2,8 +2,8 @@ import { Field } from "../Field.js";
|
||||
|
||||
/** A tag within a DMARC/DKIM record */
|
||||
export class Tag extends Field {
|
||||
isRequired = false;
|
||||
defaultValue = null;
|
||||
initialValue = null;
|
||||
position = null;
|
||||
|
||||
constructor(key) {
|
||||
@ -34,13 +34,14 @@ export class Tag extends Field {
|
||||
|
||||
// Builder methods
|
||||
|
||||
required() {
|
||||
this.isRequired = true;
|
||||
default(value) {
|
||||
this.defaultValue = value;
|
||||
this.initialValue = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
default(value) {
|
||||
this.defaultValue = value;
|
||||
initial(value) {
|
||||
this.initialValue = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
19
assets/scripts/tags/TextTag.js
Normal file
19
assets/scripts/tags/TextTag.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { Tag } from "./Tag.js";
|
||||
|
||||
export class TextTag extends Tag {
|
||||
constructor(key) {
|
||||
super(key);
|
||||
}
|
||||
|
||||
validate(value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
getInputHtml(id) {
|
||||
return `<input id="${id}" type="text" name="${this.key}" value="${this.initialValue ?? ""}" style="width: 100%">`;
|
||||
}
|
||||
|
||||
getInputValue(id) {
|
||||
return document.getElementById(id).value;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
import { DkimRecord } from "../records/DkimRecord.js";
|
||||
import { DmarcRecord } from "../records/DmarcRecord.js";
|
||||
import { SpfRecord } from "../records/SpfRecord.js";
|
||||
|
||||
const records = {
|
||||
"/dkim-creator": DkimRecord,
|
||||
"/dmarc-creator": DmarcRecord,
|
||||
"/spf-creator": SpfRecord,
|
||||
};
|
||||
@ -36,7 +38,12 @@ document.getElementById("form").onchange = generate;
|
||||
generate();
|
||||
|
||||
function generate() {
|
||||
const record = Record.createFromFieldInputs(inputs);
|
||||
const items = [];
|
||||
for (const input of inputs) {
|
||||
items.push(...input.items);
|
||||
}
|
||||
|
||||
const record = Record.createFromFieldInputItems(items);
|
||||
|
||||
document.getElementById("record").value = record.text;
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ h1, h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
label {
|
||||
body > label {
|
||||
color: #757575;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
@ -122,7 +122,7 @@ li:not(:first-of-type) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
form label {
|
||||
form > label, details > label {
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
@ -134,6 +134,7 @@ form input {
|
||||
border: 1px solid #BDBDBD;
|
||||
padding: 0.5rem 1rem;
|
||||
transition: border 200ms;
|
||||
accent-color: black;
|
||||
}
|
||||
|
||||
form input:focus {
|
||||
@ -164,3 +165,29 @@ summary {
|
||||
summary:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.field-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.text-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #757575;
|
||||
transition: color 200ms;
|
||||
}
|
||||
|
||||
.text-button:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
57
dkim-creator/index.html
Normal file
57
dkim-creator/index.html
Normal file
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DKIM Record Creator - Generate DKIM DNS Records</title>
|
||||
<link rel="stylesheet" href="/assets/styles/main.css">
|
||||
<script type="module" src="/assets/scripts/ui/creator.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>DKIM Record Creator</h1>
|
||||
|
||||
<label for="record">DNS Record</label><br>
|
||||
<input id="record" type="text" readonly>
|
||||
|
||||
<main>
|
||||
<h2>Create a DKIM DNS Record</h2>
|
||||
|
||||
<p>Customize the options below to generate a DKIM record, which will be shown in the input field above.</p>
|
||||
|
||||
<form id="form"></form>
|
||||
|
||||
<hr>
|
||||
|
||||
<p>
|
||||
This tool can be used to create DKIM DNS records, which together with a signature header can be used
|
||||
for authorizing emails.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
When setting up DKIM, you must first generate a public/private key pair. The private key is signed with
|
||||
the email message and should be automatically included in the DKIM-signature email header, while the
|
||||
public key should be pasted here in the DKIM DNS record, so that other email servers can verify the
|
||||
signature.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Using DKIM prevents your emails from ending up in the receiver's spam folder. It is recommended to also
|
||||
set up <a href="/spf-creator">SPF</a> (to authorize certain IP addresses to send mail) and
|
||||
<a href="/dmarc-creator">DMARC</a> (to specify how to handle failed validation), to minimize the risk of
|
||||
getting your emails flagged as spam.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This tool only helps with setting up the DNS record. The DKIM-signature email header should be
|
||||
automatically set by your email provider when sending mails.
|
||||
</p>
|
||||
|
||||
<center>
|
||||
<h3>More tools:</h3>
|
||||
<a href="/dkim-validator">DKIM Validator Tool</a> •
|
||||
<a href="/dmarc-creator">DMARC Creator Tool</a> •
|
||||
<a href="/spf-creator">SPF Creator Tool</a>
|
||||
</center>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@ -23,7 +23,7 @@
|
||||
<hr>
|
||||
|
||||
<p>
|
||||
This tool allows you to create DMARC DNS records, which can be used when you are hosting an email-server
|
||||
This tool allows you to create DMARC DNS records, which can be used when you are hosting an email server
|
||||
and want to provide an extra layer of security, so other email providers will trust your emails.
|
||||
</p>
|
||||
|
||||
@ -32,9 +32,16 @@
|
||||
subdomain (e.g. _dmarc.example.com) with the content being the text above.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Using DMARC prevents your emails from ending up in the receiver's spam folder by telling email servers
|
||||
how strictly to handle validation with <a href="/dkim-creator">DKIM</a> and
|
||||
<a href="/spf-creator">SPF</a>. It is highly recommended to set up both of these before using DMARC.
|
||||
</p>
|
||||
|
||||
<center>
|
||||
<h3>More tools:</h3>
|
||||
<a href="/dmarc-validator">DMARC Validator Tool</a> •
|
||||
<a href="/DKIM-creator">DKIM Creator Tool</a> •
|
||||
<a href="/spf-creator">SPF Creator Tool</a>
|
||||
</center>
|
||||
</main>
|
||||
|
||||
@ -49,9 +49,17 @@
|
||||
current IP address. See the <a href="/spf-macro-guide">Macro Guide</a> for a list of all macros.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Using SPF prevents your emails from ending up in the receiver's spam folder. It is recommended to also
|
||||
set up <a href="/dkim-creator">DKIM</a> (to authorize emails by signing them cryptographically) and
|
||||
<a href="/dmarc-creator">DMARC</a> (to specify how to handle failed validation), to minimize the risk of
|
||||
getting your emails flagged as spam.
|
||||
</p>
|
||||
|
||||
<center>
|
||||
<h3>More tools:</h3>
|
||||
<a href="/spf-validator">SPF Validator Tool</a> •
|
||||
<a href="/dkim-creator">DKIM Creator Tool</a> •
|
||||
<a href="/dmarc-creator">DMARC Creator Tool</a>
|
||||
</center>
|
||||
</main>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user