1
0
مراية لـ https://github.com/postalserver/postal.git تم المزامنة 2025-12-01 05:43:04 +00:00

feat: new configuration system (and schema) (#2819)

هذا الالتزام موجود في:
Adam Cooke
2024-02-26 12:41:57 +00:00
ملتزم من قبل GitHub
الأصل 1c5ff5a9a6
التزام 0163ac3d10
77 ملفات معدلة مع 1840 إضافات و593 حذوفات

2
.gitignore مباع
عرض الملف

@@ -31,3 +31,5 @@ Procfile.local
VERSION
.rubocop-https*
.env*

عرض الملف

@@ -24,7 +24,7 @@ bundle install
## Configuration
At present, configuration is handled using a config file. This lives in `config/postal/postal.yml`. An example configuration file is provided in `config/postal.example.yml`. This example is for development use only and not an example for production use.
Configuration is handled using a config file. This lives in `config/postal/postal.yml`. An example configuration file is provided in `config/examples/development.yml`. This example is for development use only and not an example for production use.
You'll also need a key for signing. You can generate one of these like this:
@@ -32,6 +32,10 @@ You'll also need a key for signing. You can generate one of these like this:
openssl genrsa -out config/postal/signing.key 2048
```
If you're running the tests (and you probably should be), you'll find an example file for test configuration in `config/examples/test.yml`. This should be placed in `config/postal/postal.test.yml` with the appropriate values.
If you prefer, you can configure Postal using environment variables. These should be placed in `.env` or `.env.test` as apprpriate.
## Running
The neatest way to run postal is to ensure that `./bin` is your `$PATH` and then use one of the following commands.

عرض الملف

@@ -46,8 +46,11 @@ COPY --chown=postal . .
ARG VERSION=unspecified
RUN echo $VERSION > VERSION
# Set the path to the config
ENV POSTAL_CONFIG_ROOT=/config
# Set paths for when running in a container
ENV POSTAL_CONFIG_FILE_PATH=/config/postal.yml
ENV POSTAL_SIGNING_KEY_PATH=/config/signing.key
ENV SMTP_SERVER_TLS_CERTIFICATE_PATH=/config/smtp.cert
ENV SMTP_SERVER_TLS_PRIVATE_KEY_PATH=/config/smtp.key
# Set the CMD
ENTRYPOINT [ "/docker-entrypoint.sh" ]
@@ -59,5 +62,5 @@ FROM base AS ci
# full target - default if no --target option is given
FROM base AS full
RUN POSTAL_SKIP_CONFIG_CHECK=1 RAILS_GROUPS=assets bundle exec rake assets:precompile
RUN RAILS_GROUPS=assets bundle exec rake assets:precompile
RUN touch /opt/postal/app/public/assets/.prebuilt

عرض الملف

@@ -5,7 +5,7 @@ gem "authie"
gem "autoprefixer-rails"
gem "bcrypt"
gem "chronic"
gem "dotenv-rails"
gem "dotenv"
gem "dynamic_form"
gem "encrypto_signo"
gem "execjs", "~> 2.7", "< 2.8"
@@ -15,6 +15,7 @@ gem "hashie"
gem "highline", require: false
gem "kaminari"
gem "klogger-logger"
gem "konfig-config", "~> 2.0"
gem "mail"
gem "moonrope"
gem "mysql2"

عرض الملف

@@ -102,9 +102,6 @@ GEM
deep_merge (1.2.2)
diff-lcs (1.5.0)
dotenv (3.0.2)
dotenv-rails (3.0.2)
dotenv (= 3.0.2)
railties (>= 6.1)
dynamic_form (1.3.1)
actionview (> 5.2.0)
activemodel (> 5.2.0)
@@ -151,6 +148,8 @@ GEM
concurrent-ruby (>= 1.0, < 2.0)
json
rouge (>= 3.30, < 5.0)
konfig-config (2.1.1)
hashie
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
@@ -335,7 +334,7 @@ DEPENDENCIES
chronic
coffee-rails (~> 5.0)
database_cleaner
dotenv-rails
dotenv
dynamic_form
encrypto_signo
execjs (~> 2.7, < 2.8)
@@ -347,6 +346,7 @@ DEPENDENCIES
jquery-rails
kaminari
klogger-logger
konfig-config (~> 2.0)
mail
moonrope
mysql2

عرض الملف

@@ -1,3 +1,3 @@
web: bundle exec puma -C config/puma.rb
web: unset PORT; bundle exec puma -C config/puma.rb
worker: bundle exec ruby script/worker.rb
smtp: bundle exec ruby script/smtp_server.rb
smtp: unset PORT; bundle exec ruby script/smtp_server.rb

عرض الملف

@@ -8,9 +8,9 @@ class DKIMHeader
@dkim_key = domain.dkim_key
@dkim_identifier = domain.dkim_identifier
else
@domain_name = Postal.config.dns.return_path
@domain_name = Postal::Config.dns.return_path_domain
@dkim_key = Postal.signing_key
@dkim_identifier = Postal.config.dns.dkim_identifier
@dkim_identifier = Postal::Config.dns.dkim_identifier
end
@domain = domain
@message = message

عرض الملف

@@ -48,7 +48,7 @@ module MessageDequeuer
end
def check_delivery_attempts
return if queued_message.attempts < Postal.config.general.maximum_delivery_attempts
return if queued_message.attempts < Postal::Config.postal.default_maximum_delivery_attempts
details = "Maximum number of delivery attempts (#{queued_message.attempts}) has been reached."
if queued_message.message.scope == "incoming"

عرض الملف

@@ -3,8 +3,8 @@
class ReceivedHeader
OUR_HOSTNAMES = {
smtp: Postal.config.dns.smtp_server_hostname,
http: Postal.config.web.host
smtp: Postal::Config.postal.smtp_hostname,
http: Postal::Config.postal.web_hostname
}.freeze
class << self

عرض الملف

@@ -1,6 +1,5 @@
# frozen_string_literal: true
module Postal
class ReplySeparator
RULES = [
@@ -33,4 +32,3 @@ module Postal
end
end
end

عرض الملف

@@ -32,7 +32,9 @@ module SMTPServer
end
def check_ip_address
return unless @ip_address && Postal.config.smtp_server.log_exclude_ips && @ip_address =~ Regexp.new(Postal.config.smtp_server.log_exclude_ips)
return unless @ip_address &&
Postal::Config.smtp_server.log_ip_address_exclusion_matcher &&
@ip_address =~ Regexp.new(Postal::Config.smtp_server.log_ip_address_exclusion_matcher)
@logging_enabled = false
end
@@ -109,7 +111,7 @@ module SMTPServer
@state = :welcome
log "\e[35m Client identified as #{@ip_address}\e[0m"
increment_command_count("PROXY")
"220 #{Postal.config.dns.smtp_server_hostname} ESMTP Postal/#{id}"
"220 #{Postal::Config.postal.smtp_hostname} ESMTP Postal/#{id}"
else
@finished = true
increment_error_count("proxy-error")
@@ -123,7 +125,7 @@ module SMTPServer
end
def starttls
if Postal.config.smtp_server.tls_enabled?
if Postal::Config.smtp_server.tls_enabled?
@start_tls = true
@tls = true
increment_command_count("STARTLS")
@@ -141,7 +143,7 @@ module SMTPServer
increment_command_count("EHLO")
[
"250-My capabilities are",
Postal.config.smtp_server.tls_enabled? && !@tls ? "250-STARTTLS" : nil,
Postal::Config.smtp_server.tls_enabled? && !@tls ? "250-STARTTLS" : nil,
"250 AUTH CRAM-MD5 PLAIN LOGIN"
].compact
end
@@ -151,7 +153,7 @@ module SMTPServer
transaction_reset
@state = :welcomed
increment_command_count("HELO")
"250 #{Postal.config.dns.smtp_server_hostname}"
"250 #{Postal::Config.postal.smtp_hostname}"
end
def rset
@@ -231,7 +233,7 @@ module SMTPServer
increment_command_count("AUTH CRAM-MD5")
challenge = Digest::SHA1.hexdigest(Time.now.to_i.to_s + rand(100_000).to_s)
challenge = "<#{challenge[0, 20]}@#{Postal.config.dns.smtp_server_hostname}>"
challenge = "<#{challenge[0, 20]}@#{Postal::Config.postal.smtp_hostname}>"
handler = proc do |idata|
@proc = nil
@@ -309,7 +311,7 @@ module SMTPServer
uname, tag = uname.split("+", 2)
if domain == Postal.config.dns.return_path || domain =~ /\A#{Regexp.escape(Postal.config.dns.custom_return_path_prefix)}\./
if domain == Postal::Config.dns.return_path_domain || domain =~ /\A#{Regexp.escape(Postal::Config.dns.custom_return_path_prefix)}\./
# This is a return path
@state = :rcpt_to_received
if server = ::Server.where(token: uname).first
@@ -326,7 +328,7 @@ module SMTPServer
"550 Invalid server token"
end
elsif domain == Postal.config.dns.route_domain
elsif domain == Postal::Config.dns.route_domain
# This is an email direct to a route. This isn't actually supported yet.
@state = :rcpt_to_received
if route = Route.where(token: uname).first
@@ -446,14 +448,14 @@ module SMTPServer
end
def finished
if @data.bytesize > Postal.config.smtp_server.max_message_size.megabytes.to_i
if @data.bytesize > Postal::Config.smtp_server.max_message_size.megabytes.to_i
transaction_reset
@state = :welcomed
increment_error_count("message-too-large")
return format("552 Message too large (maximum size %dMB)", Postal.config.smtp_server.max_message_size)
return format("552 Message too large (maximum size %dMB)", Postal::Config.smtp_server.max_message_size)
end
if @headers["received"].grep(/by #{Postal.config.dns.smtp_server_hostname}/).count > 4
if @headers["received"].grep(/by #{Postal::Config.postal.smtp_hostname}/).count > 4
transaction_reset
@state = :welcomed
increment_error_count("loop-detected")

عرض الملف

@@ -8,6 +8,24 @@ module SMTPServer
include HasPrometheusMetrics
class << self
def tls_private_key
@tls_private_key ||= OpenSSL::PKey.read(File.read(Postal::Config.smtp_server.tls_private_key_path))
end
def tls_certificates
@tls_certificates ||= begin
data = File.read(Postal::Config.smtp_server.tls_certificate_path)
certs = data.scan(/-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/m)
certs.map do |c|
OpenSSL::X509::Certificate.new(c)
end.freeze
end
end
end
def initialize(options = {})
@options = options
@options[:debug] ||= false
@@ -43,16 +61,19 @@ module SMTPServer
@ssl_context ||= begin
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_context.cert = Postal.smtp_certificates[0]
ssl_context.extra_chain_cert = Postal.smtp_certificates[1..]
ssl_context.key = Postal.smtp_private_key
ssl_context.ssl_version = Postal.config.smtp_server.ssl_version if Postal.config.smtp_server.ssl_version
ssl_context.ciphers = Postal.config.smtp_server.tls_ciphers if Postal.config.smtp_server.tls_ciphers
ssl_context.extra_chain_cert = self.class.tls_certificates[1..]
ssl_context.key = self.class.tls_private_key
ssl_context.ssl_version = Postal::Config.smtp_server.ssl_version if Postal::Config.smtp_server.ssl_version
ssl_context.ciphers = Postal::Config.smtp_server.tls_ciphers if Postal::Config.smtp_server.tls_ciphers
ssl_context
end
end
def listen
@server = TCPServer.open(Postal.config.smtp_server.bind_address, Postal.config.smtp_server.port)
bind_address = ENV.fetch("BIND_ADDRESS", Postal::Config.smtp_server.default_bind_address)
port = ENV.fetch("PORT", Postal::Config.smtp_server.default_port)
@server = TCPServer.open(bind_address, port)
@server.autoclose = false
@server.close_on_exec = false
if defined?(Socket::SOL_SOCKET) && defined?(Socket::SO_KEEPALIVE)
@@ -63,7 +84,8 @@ module SMTPServer
@server.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, 10)
@server.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, 5)
end
logger.info "Listening on #{Postal.config.smtp_server.bind_address}:#{Postal.config.smtp_server.port}"
logger.info "Listening on #{bind_address}:#{port}"
end
def unlisten
@@ -90,22 +112,22 @@ module SMTPServer
# Accept the connection
new_io = io.accept
increment_prometheus_counter :postal_smtp_server_connections_total
if Postal.config.smtp_server.proxy_protocol
if Postal::Config.smtp_server.proxy_protocol?
# If we are using the haproxy proxy protocol, we will be sent the
# client's IP later. Delay the welcome process.
client = Client.new(nil)
if Postal.config.smtp_server.log_connect
if Postal::Config.smtp_server.log_connections?
logger.debug "[#{client.id}] \e[35m Connection opened from #{new_io.remote_address.ip_address}\e[0m"
end
else
# We're not using the proxy protocol so we already know the client's IP
client = Client.new(new_io.remote_address.ip_address)
if Postal.config.smtp_server.log_connect
if Postal::Config.smtp_server.log_connections?
logger.debug "[#{client.id}] \e[35m Connection opened from #{new_io.remote_address.ip_address}\e[0m"
end
# We know who the client is, welcome them.
client.log "\e[35m Client identified as #{new_io.remote_address.ip_address}\e[0m"
new_io.print("220 #{Postal.config.dns.smtp_server_hostname} ESMTP Postal/#{client.id}")
new_io.print("220 #{Postal::Config.postal.smtp_hostname} ESMTP Postal/#{client.id}")
end
# Register the client and its socket with nio4r
monitor = @io_selector.register(new_io, :r)

عرض الملف

@@ -2,7 +2,7 @@
class ApplicationMailer < ActionMailer::Base
default from: "#{Postal.smtp_from_name} <#{Postal.smtp_from_address}>"
default from: "#{Postal::Config.smtp.from_name} <#{Postal::Config.smtp.from_address}>"
layout false
end

عرض الملف

@@ -14,7 +14,7 @@ class BounceMessage
mail.subject = "Mail Delivery Failed (#{@message.subject})"
mail.text_part = body
mail.attachments["Original Message.eml"] = { mime_type: "message/rfc822", encoding: "quoted-printable", content: @message.raw_message }
mail.message_id = "<#{SecureRandom.uuid}@#{Postal.config.dns.return_path}>"
mail.message_id = "<#{SecureRandom.uuid}@#{Postal::Config.dns.return_path_domain}>"
mail.to_s
end
@@ -32,7 +32,7 @@ class BounceMessage
end
def postmaster_address
@server.postmaster_address || "postmaster@#{@message.domain&.name || Postal.config.web.host}"
@server.postmaster_address || "postmaster@#{@message.domain&.name || Postal::Config.postal.web_hostname}"
end
private

عرض الملف

@@ -49,10 +49,10 @@ module HasDNSChecks
self.spf_status = "Missing"
self.spf_error = "No SPF record exists for this domain"
else
suitable_spf_records = spf_records.grep(/include:\s*#{Regexp.escape(Postal.config.dns.spf_include)}/)
suitable_spf_records = spf_records.grep(/include:\s*#{Regexp.escape(Postal::Config.dns.spf_include)}/)
if suitable_spf_records.empty?
self.spf_status = "Invalid"
self.spf_error = "An SPF record exists but it doesn't include #{Postal.config.dns.spf_include}"
self.spf_error = "An SPF record exists but it doesn't include #{Postal::Config.dns.spf_include}"
false
else
self.spf_status = "OK"
@@ -108,11 +108,11 @@ module HasDNSChecks
self.mx_status = "Missing"
self.mx_error = "There are no MX records for #{name}"
else
missing_records = Postal.config.dns.mx_records.dup - records.map { |r| r.to_s.downcase }
missing_records = Postal::Config.dns.mx_records.dup - records.map { |r| r.to_s.downcase }
if missing_records.empty?
self.mx_status = "OK"
self.mx_error = nil
elsif missing_records.size == Postal.config.dns.mx_records.size
elsif missing_records.size == Postal::Config.dns.mx_records.size
self.mx_status = "Missing"
self.mx_error = "You have MX records but none of them point to us."
else
@@ -136,12 +136,12 @@ module HasDNSChecks
if records.empty?
self.return_path_status = "Missing"
self.return_path_error = "There is no return path record at #{return_path_domain}"
elsif records.size == 1 && records.first == Postal.config.dns.return_path
elsif records.size == 1 && records.first == Postal::Config.dns.return_path_domain
self.return_path_status = "OK"
self.return_path_error = nil
else
self.return_path_status = "Invalid"
self.return_path_error = "There is a CNAME record at #{return_path_domain} but it points to #{records.first} which is incorrect. It should point to #{Postal.config.dns.return_path}."
self.return_path_error = "There is a CNAME record at #{return_path_domain} but it points to #{records.first} which is incorrect. It should point to #{Postal::Config.dns.return_path_domain}."
end
end

عرض الملف

@@ -104,7 +104,7 @@ class Domain < ApplicationRecord
end
def spf_record
"v=spf1 a mx include:#{Postal.config.dns.spf_include} ~all"
"v=spf1 a mx include:#{Postal::Config.dns.spf_include} ~all"
end
def dkim_record
@@ -117,7 +117,7 @@ class Domain < ApplicationRecord
def dkim_identifier
return nil unless dkim_identifier_string
Postal.config.dns.dkim_identifier + "-#{dkim_identifier_string}"
Postal::Config.dns.dkim_identifier + "-#{dkim_identifier_string}"
end
def dkim_record_name
@@ -128,7 +128,7 @@ class Domain < ApplicationRecord
end
def return_path_domain
"#{Postal.config.dns.custom_return_path_prefix}.#{name}"
"#{Postal::Config.dns.custom_return_path_prefix}.#{name}"
end
# Returns a DNSResolver instance that can be used to perform DNS lookups needed for
@@ -136,13 +136,13 @@ class Domain < ApplicationRecord
#
# @return [DNSResolver]
def resolver
return DNSResolver.local if Postal.config.general.use_local_ns_for_domains?
return DNSResolver.local if Postal::Config.postal.use_local_ns_for_domain_verification?
@resolver ||= DNSResolver.for_domain(name)
end
def dns_verification_string
"#{Postal.config.dns.domain_verify_prefix} #{verification_token}"
"#{Postal::Config.dns.domain_verify_prefix} #{verification_token}"
end
def verify_with_dns

عرض الملف

@@ -88,7 +88,7 @@ class IncomingMessagePrototype
mail.from = @from
mail.subject = @subject
mail.text_part = @plain_body
mail.message_id = "<#{SecureRandom.uuid}@#{Postal.config.dns.return_path}>"
mail.message_id = "<#{SecureRandom.uuid}@#{Postal::Config.dns.return_path_domain}>"
attachments.each do |attachment|
mail.attachments[attachment[:name]] = {
mime_type: attachment[:content_type],

عرض الملف

@@ -25,7 +25,7 @@ class OutgoingMessagePrototype
@source_type = source_type
@custom_headers = {}
@attachments = []
@message_id = "#{SecureRandom.uuid}@#{Postal.config.dns.return_path}"
@message_id = "#{SecureRandom.uuid}@#{Postal::Config.dns.return_path_domain}"
attributes.each do |key, value|
instance_variable_set("@#{key}", value)
end

عرض الملف

@@ -89,7 +89,7 @@ class Route < ApplicationRecord
end
def forward_address
@forward_address ||= "#{token}@#{Postal.config.dns.route_domain}"
@forward_address ||= "#{token}@#{Postal::Config.dns.route_domain}"
end
def wildcard?

عرض الملف

@@ -71,8 +71,8 @@ class Server < ApplicationRecord
default_value :raw_message_retention_days, -> { 30 }
default_value :raw_message_retention_size, -> { 2048 }
default_value :message_retention_days, -> { 60 }
default_value :spam_threshold, -> { Postal.config.general.default_spam_threshold }
default_value :spam_failure_threshold, -> { Postal.config.general.default_spam_failure_threshold }
default_value :spam_threshold, -> { Postal::Config.postal.default_spam_threshold }
default_value :spam_failure_threshold, -> { Postal::Config.postal.default_spam_failure_threshold }
validates :name, presence: true, uniqueness: { scope: :organization_id, case_sensitive: false }
validates :mode, inclusion: { in: MODES }

عرض الملف

@@ -58,12 +58,12 @@ class TrackDomain < ApplicationRecord
if records.empty?
self.dns_status = "Missing"
self.dns_error = "There is no record at #{full_name}"
elsif records.size == 1 && records.first == Postal.config.dns.track_domain
elsif records.size == 1 && records.first == Postal::Config.dns.track_domain
self.dns_status = "OK"
self.dns_error = nil
else
self.dns_status = "Invalid"
self.dns_error = "There is a CNAME record at #{full_name} but it points to #{records.first} which is incorrect. It should point to #{Postal.config.dns.track_domain}."
self.dns_error = "There is a CNAME record at #{full_name} but it points to #{records.first} which is incorrect. It should point to #{Postal::Config.dns.track_domain}."
end
self.dns_checked_at = Time.now
save!

عرض الملف

@@ -47,8 +47,8 @@ class SMTPSender < BaseSender
end
smtp_client = Net::SMTP.new(@remote_ip, port)
smtp_client.open_timeout = Postal.config.smtp_client.open_timeout
smtp_client.read_timeout = Postal.config.smtp_client.read_timeout
smtp_client.open_timeout = Postal::Config.smtp_client.open_timeout
smtp_client.read_timeout = Postal::Config.smtp_client.read_timeout
smtp_client.tls_hostname = hostname
if @source_ip_address
@@ -140,9 +140,9 @@ class SMTPSender < BaseSender
elsif message.domain.return_path_status == "OK"
mail_from = "#{message.server.token}@#{message.domain.return_path_domain}"
else
mail_from = "#{message.server.token}@#{Postal.config.dns.return_path}"
mail_from = "#{message.server.token}@#{Postal::Config.dns.return_path_domain}"
end
if Postal.config.general.use_resent_sender_header
if Postal::Config.postal.use_resent_sender_header
raw_message = "Resent-Sender: #{mail_from}\r\n" + message.raw_message
else
raw_message = message.raw_message
@@ -270,15 +270,20 @@ class SMTPSender < BaseSender
end
def default_helo_hostname
Postal.config.dns.helo_hostname || Postal.config.dns.smtp_server_hostname || "localhost"
Postal::Config.dns.helo_hostname ||
Postal::Config.postal.smtp_hostname ||
"localhost"
end
def relay_hosts
hosts = Postal.config.smtp_relays.map do |relay|
next unless relay.hostname.present?
relays = Postal::Config.postal.smtp_relays
return nil if relays.nil?
hosts = relays.map do |relay|
next unless relay.host.present?
{
hostname: relay.hostname,
hostname: relay.host,
port: relay.port,
ssl_mode: relay.ssl_mode
}

عرض الملف

@@ -51,8 +51,9 @@ class HealthServer
class << self
def run(default_port: 9090, **options)
def run(default_port:, default_bind_address:, **options)
port = ENV.fetch("HEALTH_SERVER_PORT", default_port)
bind_address = ENV.fetch("HEALTH_SERVER_BIND_ADDRESS", default_bind_address)
Rack::Handler::WEBrick.run(new(**options),
Port: port,
@@ -64,10 +65,6 @@ class HealthServer
"in use, not starting health server"
end
def bind_address
ENV.fetch("HEALTH_SERVER_BIND_ADDRESS", "127.0.0.1")
end
def start(**options)
thread = Thread.new { run(**options) }
thread.abort_on_exception = false

عرض الملف

@@ -8,5 +8,5 @@ If you didn't request this, you can ignore this e-mail.
Thanks,
<%= Postal.smtp_from_name %>
<%= Postal.smtp_from_address %>
<%= Postal::Config.smtp.from_name %>
<%= Postal::Config.smtp.from_address %>

عرض الملف

@@ -13,5 +13,5 @@ You can view more information about this server at:
Thanks,
<%= Postal.smtp_from_name %>
<%= Postal.smtp_from_address %>
<%= Postal::Config.smtp.from_name %>
<%= Postal::Config.smtp.from_address %>

عرض الملف

@@ -13,5 +13,5 @@ You can view more information about this server at:
Thanks,
<%= Postal.smtp_from_name %>
<%= Postal.smtp_from_address %>
<%= Postal::Config.smtp.from_name %>
<%= Postal::Config.smtp.from_address %>

عرض الملف

@@ -8,5 +8,5 @@ Reason: <%= @server.actual_suspension_reason %>
Thanks,
<%= Postal.smtp_from_name %>
<%= Postal.smtp_from_address %>
<%= Postal::Config.smtp.from_name %>
<%= Postal::Config.smtp.from_address %>

عرض الملف

@@ -10,5 +10,5 @@ If you don't agree, just ignore this e-mail.
Thanks,
<%= Postal.smtp_from_name %>
<%= Postal.smtp_from_address %>
<%= Postal::Config.smtp.from_name %>
<%= Postal::Config.smtp.from_address %>

عرض الملف

@@ -43,7 +43,7 @@
%p.pageContent__text
You need to add a TXT record at the apex/root of your domain (@) with the following
content. If you already send mail from another service, you may just need to add
<b>include:#{Postal.config.dns.spf_include}</b> to your existing record.
<b>include:#{Postal::Config.dns.spf_include}</b> to your existing record.
%pre.codeBlock.u-margin= @domain.spf_record
%h3.pageContent__subTitle DKIM Record
@@ -78,7 +78,7 @@
%p.pageContent__text
This is optional but we recommend adding this to improve deliverability. You should add
a <b>CNAME</b> record at <b>#{@domain.return_path_domain}</b> to point to the hostname below.
%pre.codeBlock.u-margin= Postal.config.dns.return_path
%pre.codeBlock.u-margin= Postal::Config.dns.return_path_domain
%h3.pageContent__subTitle MX Records
@@ -99,6 +99,4 @@
If you wish to receive incoming e-mail for this domain, you need to add the following MX records
to the domain. You don't have to do this and we'll only tell you if they're set up or not. Both
records should be priority <b>10</b>.
%pre.codeBlock.u-margin= Postal.config.dns.mx_records.join("\n")
%pre.codeBlock.u-margin= Postal::Config.dns.mx_records.join("\n")

عرض الملف

@@ -36,6 +36,5 @@
%dl.pageContent__definitions
%dt MX Records
%dd
- for mx in Postal.config.dns.mx_records
- for mx in Postal::Config.dns.mx_records
%p.pageContent__definitionCode= mx

عرض الملف

@@ -18,7 +18,7 @@
Mail servers can be enabled to send mail from any domain by the administrator.
%li
If a message cannot be delivered, the system will not send you a bounce message but dispatch a webhook (if you set one up).
If a message delivery fails but can be retried, the system will try #{Postal.config.general.maximum_delivery_attempts} times to deliver it before giving up.
If a message delivery fails but can be retried, the system will try #{Postal::Config.postal.default_maximum_delivery_attempts} times to deliver it before giving up.
.u-margin
%h2.pageContent__subTitle Sending using SMTP
%p.pageContent__text
@@ -27,10 +27,10 @@
%dl.pageContent__definitions
%dt SMTP Server Address
%dd
%p.pageContent__definitionCode= Postal.config.dns.smtp_server_hostname
%p.pageContent__definitionCode= Postal::Config.postal.smtp_hostname
%dt Port
%dd
%p.pageContent__definitionCode= Postal.config.smtp_server.port
%p.pageContent__definitionCode= Postal::Config.smtp_server.default_port
%p.pageContent__definitionText
The SMTP service supports STARTTLS if you wish to send messages securely. Be aware that security
cannot guaranteed all the way to their final destination.

عرض الملف

@@ -14,7 +14,7 @@
- else
%p.pageContent__intro.u-margin
When messages cannot be delivered, addresses are added to the suppression list which stops
future messages to the same recipient being sent through. Recipients are removed from the list after #{Postal.config.general.suppression_list_removal_delay} days.
future messages to the same recipient being sent through. Recipients are removed from the list after #{Postal::Config.postal.default_suppression_list_automatic_removal_days} days.
%ul.suppressionList
- for suppression in @suppressions[:records]
%li.suppressionList__item

عرض الملف

@@ -10,7 +10,7 @@
= f.select :domain_id, domain_options_for_select(@server, @track_domain.domain), {}, :class => 'input input--select routeNameInput__domain', :disabled => @track_domain.persisted?
%p.fieldSet__text
This is the domain that requests for tracked links will be directed through when you use click tracking. We recommend using something like
<b>click.yourdomain.com</b>. You will need to a CNAME record to point to <b>#{Postal.config.dns.track_domain}</b> for this once you've added it.
<b>click.yourdomain.com</b>. You will need to a CNAME record to point to <b>#{Postal::Config.dns.track_domain}</b> for this once you've added it.
.fieldSet__field
= f.label :ssl_enabled, :class => 'fieldSet__label'
@@ -47,4 +47,3 @@
.fieldSetSubmit.buttonSet
= f.submit @track_domain.new_record? ? "Create Track Domain" : "Save Track Domain", :class => 'button button--positive js-form-submit'

عرض الملف

@@ -3,5 +3,4 @@
# This file is used by Rack-based servers to start the application.
require_relative "config/environment"
$0 = "[postal] #{ENV.fetch('PROC_NAME', nil)}"
run Rails.application

عرض الملف

@@ -38,9 +38,9 @@ module Postal
require "tracking_middleware"
config.middleware.insert_before ActionDispatch::HostAuthorization, TrackingMiddleware
config.hosts << Postal.config.web.host
config.hosts << Postal::Config.postal.web_hostname
if Postal.config.logging.rails_log == false
unless Postal::Config.logging.rails_log_enabled?
config.logger = Logger.new("/dev/null")
end

عرض الملف

@@ -5,6 +5,5 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
require "bundler/setup" # Set up gems listed in the Gemfile.
require_relative "../lib/postal/config"
Postal.check_config!
ENV["RAILS_ENV"] = Postal.config.rails&.environment || "development"
ENV["RAILS_ENV"] = Postal::Config.rails.environment || "development"

عرض الملف

@@ -1,13 +1,13 @@
default: &default
adapter: mysql2
reconnect: true
encoding: <%= Postal.config.main_db.encoding %>
pool: <%= Postal.config.main_db.pool_size %>
username: <%= Postal.config.main_db.username %>
password: <%= Postal.config.main_db.password %>
host: <%= Postal.config.main_db.host %>
port: <%= Postal.config.main_db.port %>
database: <%= Postal.config.main_db.database %>
encoding: "<%= Postal::Config.main_db.encoding %>"
pool: <%= Postal::Config.main_db.pool_size %>
username: "<%= Postal::Config.main_db.username %>"
password: "<%= Postal::Config.main_db.password %>"
host: "<%= Postal::Config.main_db.host %>"
port: <%= Postal::Config.main_db.port %>
database: "<%= Postal::Config.main_db.database %>"
development:
<<: *default

عرض الملف

@@ -1,17 +1,13 @@
web:
host: postal.example.dev
protocol: https
# This is an example Postal configuration file for use in
# development environments. For a production example, see
# the https://github.com/postalserver/install repository.
web_server:
bind_address: 0.0.0.0
port: 4010
version: 2
smtp_server:
port: 2525
logging:
rails_log: true
stdout: true
postal:
web_hostname: postal.example.com
web_protocol: https
smtp_hostname: postal.example.com
main_db:
host: 127.0.0.1
@@ -25,13 +21,9 @@ message_db:
password:
prefix: postal
smtp:
host: 127.0.0.1
port: 2525
username:
password:
from_name: Postal
from_address: postal@yourdomain.com
logging:
rails_log_enabled: true
highlighting_enabled: true
rails:
environment: development

25
config/examples/test.yml Normal file
عرض الملف

@@ -0,0 +1,25 @@
# This is an example Postal configuration file for use in
# test environments. For a production example, see
# the https://github.com/postalserver/install repository.
version: 2
main_db:
host: 127.0.0.1
username: root
password:
database: postal-test
message_db:
host: 127.0.0.1
username: root
password:
prefix: postal-test
logging:
enabled: false
rails_log_enabled: false
rails:
environment: test
secret_key: 7f27856d26e864bafd49d0df37ad3d1339086e86ef0447e0f1814dde5277452fea97dab9e3aad6dfa11bfe359c82ce302d97bf1e58f6103c4408e4fbad4eeccf

عرض الملف

@@ -1,7 +1,7 @@
# frozen_string_literal: true
if Postal.config.rails&.secret_key
Rails.application.secrets.secret_key_base = Postal.config.rails.secret_key
if Postal::Config.rails.secret_key
Rails.application.secrets.secret_key_base = Postal::Config.rails.secret_key
else
warn "No secret key was specified in the Postal config file. Using one for just this session"
Rails.application.secrets.secret_key_base = SecureRandom.hex(128)

عرض الملف

@@ -2,8 +2,8 @@
require "postal/config"
if Postal.config.general&.exception_url
if Postal::Config.logging.sentry_dsn
Sentry.init do |config|
config.dsn = Postal.config.general.exception_url
config.dsn = Postal::Config.logging.sentry_dsn
end
end

عرض الملف

@@ -2,9 +2,10 @@
require "postal/config"
if Postal.config&.smtp
# TODO: by default, we should just send mail through the local Postal
# installation rather than having to actually configure an SMTP server.
ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = { address: Postal.config.smtp.host, user_name: Postal.config.smtp.username, password: Postal.config.smtp.password, port: Postal.config.smtp.port || 25 }
end
ActionMailer::Base.smtp_settings = {
address: Postal::Config.smtp.host,
user_name: Postal::Config.smtp.username,
password: Postal::Config.smtp.password,
port: Postal::Config.smtp.port
}

عرض الملف

@@ -1,111 +0,0 @@
# These are the default configuration options that will be used if they aren't overriden
# in your postal.yml configuration file. No changes should be made to this file for
# your installation.
# You can refer to this for a complete listing all available configuration options.
web:
host: <%= ENV.fetch('POSTAL_HOST', 'postal.example.com') %>
protocol: <%= ENV.fetch('POSTAL_PROTOCOL', 'https') %>
general:
use_ip_pools: <%= ENV.fetch('POSTAL_USE_IP_POOLS', 'false') %>
exception_url: <%= ENV.fetch('POSTAL_EXCEPTION_URL', '') %>
maximum_delivery_attempts: <%= ENV.fetch('POSTAL_MAXIMUM_DELIVERY_ATTEMPTS', '18') %>
maximum_hold_expiry_days: <%= ENV.fetch('POSTAL_MAXIMUM_HOLD_EXPIRY_DAYS', '7') %>
suppression_list_removal_delay: <%= ENV.fetch('POSTAL_SUPPRESSION_LIST_REMOVAL_DELAY', '30') %>
use_local_ns_for_domains: <%= ENV.fetch('POSTAL_USE_LOCAL_NS_FOR_DOMAINS', 'false') %>
default_spam_threshold: <%= ENV.fetch('POSTAL_DEFAULT_SPAM_THRESHOLD', '5.0') %>
default_spam_failure_threshold: <%= ENV.fetch('POSTAL_DEFAULT_SPAM_FAILURE_THRESHOLD', '20.0') %>
use_resent_sender_header: <%= ENV.fetch('POSTAL_USE_RESENT_SENDER_HEADER', 'true') %>
web_server:
bind_address: <%= ENV.fetch('WEB_SERVER_BIND_ADDRESS', '0.0.0.0') %>
port: <%= ENV.fetch('WEB_SERVER_PORT', '5000') %>
max_threads: <%= ENV.fetch('WEB_SERVER_MAX_THREADS', '5') %>
main_db:
host: <%= ENV.fetch('MAIN_DB_HOST', '127.0.0.1') %>
port: <%= ENV.fetch('MAIN_DB_PORT', '3306') %>
username: <%= ENV.fetch('MAIN_DB_USERNAME', 'postal') %>
password: <%= ENV.fetch('MAIN_DB_PASSWORD', '') %>
database: <%= ENV.fetch('MAIN_DB_DATABASE', 'postal') %>
pool_size: <%= ENV.fetch('MAIN_DB_POOL_SIZE', '5') %>
encoding: <%= ENV.fetch('MAIN_DB_ENCODING', 'utf8mb4') %>
message_db:
host: <%= ENV.fetch('MESSAGE_DB_HOST', '127.0.0.1') %>
port: <%= ENV.fetch('MESSAGE_DB_PORT', '3306') %>
username: <%= ENV.fetch('MESSAGE_DB_USERNAME', 'postal') %>
password: <%= ENV.fetch('MESSAGE_DB_PASSWORD', '') %>
prefix: <%= ENV.fetch('MESSAGE_DB_PREFIX', 'postal') %>
logging:
rails_log: <%= ENV.fetch('LOGGING_RAILS_LOG', 'false') %>
graylog:
host: <%= ENV.fetch('GRAYLOG_HOST', '') %>
port: <%= ENV.fetch('GRAYLOG_PORT', '12201') %>
facility: <%= ENV.fetch('GRAYLOG_FACILITY', 'postal') %>
smtp_server:
port: <%= ENV.fetch('SMTP_SERVER_PORT', '25') %>
bind_address: "<%= ENV.fetch('SMTP_SERVER_BIND_ADDRESS', '::') %>"
tls_enabled: <%= ENV.fetch('SMTP_SERVER_TLS_ENABLED', 'false') %>
tls_certificate_path: <%= ENV.fetch('SMTP_SERVER_TLS_CERTIFICATE_PATH', '') %> # Defaults to config/smtp.cert
tls_private_key_path: <%= ENV.fetch('SMTP_SERVER_TLS_PRIVATE_KEY_PATH', '') %> # Defaults to config/smtp.key
tls_ciphers: <%= ENV.fetch('SMTP_SERVER_TLS_CIPHERS', '') %>
ssl_version: <%= ENV.fetch('SMTP_SERVER_SSL_VERSION', 'SSLv23') %>
proxy_protocol: <%= ENV.fetch('SMTP_SERVER_PROXY_PROTOCOL', 'false') %>
log_connect: <%= ENV.fetch('SMTP_SERVER_LOG_CONNECT', 'false') %>
max_message_size: <%= ENV.fetch('SMTP_SERVER_MAX_MESSAGE_SIZE', '14') %> # size in Megabytes
smtp_relays:
- hostname:
port: 25
ssl_mode: Auto
dns:
mx_records: <%= ENV.fetch('DNS_MX_RECORDS', 'mx.postal.example.com'.split(',').inspect) %>
smtp_server_hostname: <%= ENV.fetch('DNS_SMTP_SERVER_HOSTNAME', 'postal.example.com') %>
spf_include: <%= ENV.fetch('DNS_SPF_INCLUDE', 'spf.postal.example.com') %>
return_path: <%= ENV.fetch('DNS_RETURN_PATH', 'rp.postal.example.com') %>
route_domain: <%= ENV.fetch('DNS_ROUTE_DOMAIN', 'routes.postal.example.com') %>
track_domain: <%= ENV.fetch('DNS_TRACK_DOMAIN', 'track.postal.example.com') %>
helo_hostname: <%= ENV.fetch('DNS_HELO_HOSTNAME', '') %> # By default, this will be the same as the `smtp_server_hostname`
dkim_identifier: <%= ENV.fetch('DNS_DKIM_IDENTIFIER', 'postal') %>
domain_verify_prefix: <%= ENV.fetch('DNS_DOMAIN_VERIFY_PREFIX', 'postal-verification') %>
custom_return_path_prefix: <%= ENV.fetch('DNS_CUSTOM_RETURN_PATH_PREFIX', 'psrp') %>
smtp:
host: <%= ENV.fetch('SMTP_HOST', '127.0.0.1') %>
port: <%= ENV.fetch('SMTP_PORT', '25') %>
username: <%= ENV.fetch('SMTP_USERNAME', '') %> # Complete when Postal is running and you can
password: <%= ENV.fetch('SMTP_PASSWORD', '') %> # generate the credentials within the interface.
from_name: <%= ENV.fetch('SMTP_FROM_NAME', 'Postal') %>
from_address: <%= ENV.fetch('SMTP_FROM_ADDRESS', 'postal@example.com') %>
rails:
environment: <%= ENV.fetch('RAILS_ENV', 'production') %>
secret_key: <%= ENV.fetch('RAILS_SECRET_KEY_BASE', '') %>
rspamd:
enabled: <%= ENV.fetch('RSPAMD_ENABLED', 'false') %>
host: <%= ENV.fetch('RSPAMD_HOST', '127.0.0.1') %>
port: <%= ENV.fetch('RSPAMD_PORT', '11334') %>
ssl: <%= ENV.fetch('RSPAMD_SSL', 'false') %>
password: <%= ENV.fetch('RSPAMD_PASSWORD', '') %>
flags: <%= ENV.fetch('RSPAMD_FLAGS', '') %>
spamd:
enabled: <%= ENV.fetch('SPAMD_ENABLED', 'false') %>
host: <%= ENV.fetch('SPAMD_HOST', '127.0.0.1') %>
port: <%= ENV.fetch('SPAMD_PORT', '783') %>
clamav:
enabled: <%= ENV.fetch('CLAMAV_ENABLED', 'false') %>
host: <%= ENV.fetch('CLAMAV_HOST', '127.0.0.1') %>
port: <%= ENV.fetch('CLAMAV_PORT', '2000') %>
smtp_client:
open_timeout: <%= ENV.fetch('SMTP_CLIENT_OPEN_TIMEOUT', '30') %>
read_timeout: <%= ENV.fetch('SMTP_CLIENT_READ_TIMEOUT', '60') %>

عرض الملف

@@ -1,11 +1,12 @@
# frozen_string_literal: true
require_relative "../lib/postal/config"
threads_count = Postal.config.web_server&.max_threads&.to_i || 5
threads_count = Postal::Config.web_server.max_threads
threads threads_count, threads_count
bind_address = Postal.config.web_server&.bind_address || "127.0.0.1"
bind_port = Postal.config.web_server&.port&.to_i || ENV["PORT"] || 5000
bind_address = ENV.fetch("BIND_ADDRESS", Postal::Config.web_server.default_bind_address)
bind_port = ENV.fetch("PORT", Postal::Config.web_server.default_port)
bind "tcp://#{bind_address}:#{bind_port}"
environment Postal.config.rails&.environment || "development"
environment Postal::Config.rails.environment || "development"
prune_bundler
quiet false

عرض الملف

@@ -0,0 +1,31 @@
# Configuring Postal
Postal can be configured in two ways: using a YAML-based configuration file or through environment variables.
If you choose to use environment variables, you don't need to provide a config file. A full list of environment variables is available in the `environment-variables.md` file in this directory.
To use a configuration file, the `POSTAL_CONFIG_FILE_PATH` environment variable will dictate where Postal will look for the config file. An example YAML file containing all available configuration is provided in the `yaml.yml` file in this directory. Remember to include the `version: 2` key/value in your configuration file.
## Development
When developing with Postal, you can configure the application by placing a configuration file in `config/postal/postal.yml`. Alternatively, you can use environment variables by placing configuration in `.env` in the root of the application.
### Running tests
By default, tests will use the `config/postal/postal.test.yml` configuration file and the `.env.test` environment file.
## Containers
Within a container, Postal will for a config file in `/config/postal.yml` unless overriden by the `POSTAL_CONFIG_FILE_PATH` environment variable.
## Ports & Bind Addresses
The web & SMTP server listen on ports and addresses. The defaults for these can be set through configuration however, if you're running multiple instances of these on a single host you will need to specify different ports for each one.
You can use the `PORT` and `BIND_ADDRESS` environment variables to provide instance-specific values for these processes.
Additionally, `HEALTH_SERVER_PORT` and `HEALTH_SERVER_BIND_ADDRESS` can be used to set the port/address to use for running the health server alongside other processes.
## Legacy configuration
Legacy configuration files from Postal v1 and v2 are still supported. If you wish to use a new configuration option that is not available in the legacy format, you will need to upgrade the file to version 2.

عرض الملف

@@ -0,0 +1,90 @@
# Environment Variables
This document contains all the environment variables which are available for this application.
| Name | Type | Description | Default |
| ---- | ---- | ----------- | ------- |
| `POSTAL_WEB_HOSTNAME` | String | The hostname that the Postal web interface runs on | postal.example.com |
| `POSTAL_WEB_PROTOCOL` | String | The HTTP protocol to use for the Postal web interface | https |
| `POSTAL_SMTP_HOSTNAME` | String | The hostname that the Postal SMTP server runs on | postal.example.com |
| `POSTAL_USE_IP_POOLS` | Boolean | Should IP pools be enabled for this installation? | false |
| `POSTAL_DEFAULT_MAXIMUM_DELIVERY_ATTEMPTS` | Integer | The maximum number of delivery attempts | 18 |
| `POSTAL_DEFAULT_MAXIMUM_HOLD_EXPIRY_DAYS` | Integer | The number of days to hold a message before they will be expired | 7 |
| `POSTAL_DEFAULT_SUPPRESSION_LIST_AUTOMATIC_REMOVAL_DAYS` | Integer | The number of days an address will remain in a suppression list before being removed | 30 |
| `POSTAL_DEFAULT_SPAM_THRESHOLD` | Integer | The default threshold at which a message should be treated as spam | 5 |
| `POSTAL_DEFAULT_SPAM_FAILURE_THRESHOLD` | Integer | The default threshold at which a message should be treated as spam failure | 20 |
| `POSTAL_USE_LOCAL_NS_FOR_DOMAIN_VERIFICATION` | Boolean | Domain verification and checking usually checks with a domain's nameserver. Enable this to check with the server's local nameservers. | false |
| `POSTAL_USE_RESENT_SENDER_HEADER` | Boolean | Append a Resend-Sender header to all outgoing e-mails | true |
| `POSTAL_SIGNING_KEY_PATH` | String | Path to the private key used for signing | config/postal/signing.key |
| `POSTAL_SMTP_RELAYS` | Array of strings | An array of SMTP relays in the format of smtp://host:port | |
| `WEB_SERVER_DEFAULT_PORT` | Integer | The default port the web server should listen on unless overriden by the PORT environment variable | 5000 |
| `WEB_SERVER_DEFAULT_BIND_ADDRESS` | String | The default bind address the web server should listen on unless overriden by the BIND_ADDRESS environment variable | 127.0.0.1 |
| `WEB_SERVER_MAX_THREADS` | Integer | The maximum number of threads which can be used by the web server | 5 |
| `WORKER_DEFAULT_HEALTH_SERVER_PORT` | Integer | The default port for the worker health server to listen on | 9090 |
| `WORKER_DEFAULT_HEALTH_SERVER_BIND_ADDRESS` | String | The default bind address for the worker health server to listen on | 127.0.0.1 |
| `MAIN_DB_HOST` | String | Hostname for the main MariaDB server | localhost |
| `MAIN_DB_PORT` | Integer | The MariaDB port to connect to | 3306 |
| `MAIN_DB_USERNAME` | String | The MariaDB username | postal |
| `MAIN_DB_PASSWORD` | String | The MariaDB password | |
| `MAIN_DB_DATABASE` | String | The MariaDB database name | postal |
| `MAIN_DB_POOL_SIZE` | Integer | The maximum size of the MariaDB connection pool | 5 |
| `MAIN_DB_ENCODING` | String | The encoding to use when connecting to the MariaDB database | utf8mb4 |
| `MESSAGE_DB_HOST` | String | Hostname for the MariaDB server which stores the mail server databases | localhost |
| `MESSAGE_DB_PORT` | Integer | The MariaDB port to connect to | 3306 |
| `MESSAGE_DB_USERNAME` | String | The MariaDB username | postal |
| `MESSAGE_DB_PASSWORD` | String | The MariaDB password | |
| `MESSAGE_DB_DATABASE_NAME_PREFIX` | String | The MariaDB prefix to add to database names | postal |
| `LOGGING_RAILS_LOG_ENABLED` | Boolean | Enable the default Rails logger | false |
| `LOGGING_SENTRY_DSN` | String | A DSN which should be used to report exceptions to Sentry | |
| `LOGGING_ENABLED` | Boolean | Enable the Postal logger to log to STDOUT | true |
| `LOGGING_HIGHLIGHTING_ENABLED` | Boolean | Enable highlighting of log lines | false |
| `GELF_HOST` | String | GELF-capable host to send logs to | |
| `GELF_PORT` | Integer | GELF port to send logs to | 12201 |
| `GELF_FACILITY` | String | The facility name to add to all log entries sent to GELF | postal |
| `SMTP_SERVER_DEFAULT_PORT` | Integer | The default port the SMTP server should listen on unless overriden by the PORT environment variable | 25 |
| `SMTP_SERVER_DEFAULT_BIND_ADDRESS` | String | The default bind address the SMTP server should listen on unless overriden by the BIND_ADDRESS environment variable | 127.0.0.1 |
| `SMTP_SERVER_DEFAULT_HEALTH_SERVER_PORT` | Integer | The default port for the SMTP server health server to listen on | 9091 |
| `SMTP_SERVER_DEFAULT_HEALTH_SERVER_BIND_ADDRESS` | String | The default bind address for the SMTP server health server to listen on | 127.0.0.1 |
| `SMTP_SERVER_TLS_ENABLED` | Boolean | Enable TLS for the SMTP server (requires certificate) | false |
| `SMTP_SERVER_TLS_CERTIFICATE_PATH` | String | The path to the SMTP server's TLS certificate | config/postal/smtp.cert |
| `SMTP_SERVER_TLS_PRIVATE_KEY_PATH` | String | The path to the SMTP server's TLS private key | config/postal/smtp.key |
| `SMTP_SERVER_TLS_CIPHERS` | String | Override ciphers to use for SSL | |
| `SMTP_SERVER_SSL_VERSION` | String | The SSL versions which are supported | SSLv23 |
| `SMTP_SERVER_PROXY_PROTOCOL` | Boolean | Enable proxy protocol for use behind some load balancers | false |
| `SMTP_SERVER_LOG_CONNECTIONS` | Boolean | Enable connection logging | false |
| `SMTP_SERVER_MAX_MESSAGE_SIZE` | Integer | The maximum message size to accept from the SMTP server (in MB) | 14 |
| `SMTP_SERVER_LOG_IP_ADDRESS_EXCLUSION_MATCHER` | String | A regular expression to use to exclude connections from logging | |
| `DNS_MX_RECORDS` | Array of strings | The names of the default MX records | ["mx1.postal.example.com", "mx2.postal.example.com"] |
| `DNS_SPF_INCLUDE` | String | The location of the SPF record | spf.postal.example.com |
| `DNS_RETURN_PATH_DOMAIN` | String | The return path hostname | rp.postal.example.com |
| `DNS_ROUTE_DOMAIN` | String | The domain to use for hosting route-specific addresses | routes.postal.example.com |
| `DNS_TRACK_DOMAIN` | String | The CNAME which tracking domains should be pointed to | track.postal.example.com |
| `DNS_HELO_HOSTNAME` | String | The hostname to use in HELO/EHLO when connecting to external SMTP servers | |
| `DNS_DKIM_IDENTIFIER` | String | The identifier to use for DKIM keys in DNS records | postal |
| `DNS_DOMAIN_VERIFY_PREFIX` | String | The prefix to add before TXT record verification string | postal-verification |
| `DNS_CUSTOM_RETURN_PATH_PREFIX` | String | The domain to use on external domains which points to the Postal return path domain | psrp |
| `SMTP_HOST` | String | The hostname to send application-level e-mails to | 127.0.0.1 |
| `SMTP_PORT` | Integer | The port number to send application-level e-mails to | 25 |
| `SMTP_USERNAME` | String | The username to use when authentication to the SMTP server | |
| `SMTP_PASSWORD` | String | The password to use when authentication to the SMTP server | |
| `SMTP_FROM_NAME` | String | The name to use as the from name outgoing emails from Postal | Postal |
| `SMTP_FROM_ADDRESS` | String | The e-mail to use as the from address outgoing emails from Postal | postal@example.com |
| `RAILS_ENVIRONMENT` | String | The Rails environment to run the application in | production |
| `RAILS_SECRET_KEY` | String | The secret key used to sign and encrypt cookies and session data in the application | |
| `RSPAMD_ENABLED` | Boolean | Enable rspamd for message inspection | false |
| `RSPAMD_HOST` | String | The hostname of the rspamd server | 127.0.0.1 |
| `RSPAMD_PORT` | Integer | The port of the rspamd server | 11334 |
| `RSPAMD_SSL` | Boolean | Enable SSL for the rspamd connection | false |
| `RSPAMD_PASSWORD` | String | The password for the rspamd server | |
| `RSPAMD_FLAGS` | String | Any flags for the rspamd server | |
| `SPAMD_ENABLED` | Boolean | Enable SpamAssassin for message inspection | false |
| `SPAMD_HOST` | String | The hostname for the SpamAssassin server | 127.0.0.1 |
| `SPAMD_PORT` | Integer | The port of the SpamAssassin server | 783 |
| `CLAMAV_ENABLED` | Boolean | Enable ClamAV for message inspection | false |
| `CLAMAV_HOST` | String | The host of the ClamAV server | 127.0.0.1 |
| `CLAMAV_PORT` | Integer | The port of the ClamAV server | 2000 |
| `SMTP_CLIENT_OPEN_TIMEOUT` | Integer | The open timeout for outgoing SMTP connections | 30 |
| `SMTP_CLIENT_READ_TIMEOUT` | Integer | The read timeout for outgoing SMTP connections | 30 |
| `MIGRATION_WAITER_ENABLED` | Boolean | Wait for all migrations to run before starting a process | false |
| `MIGRATION_WAITER_ATTEMPTS` | Integer | The number of attempts to try waiting for migrations to complete before start | 120 |
| `MIGRATION_WAITER_SLEEP_TIME` | Integer | The number of seconds to wait between each migration check | 2 |

203
doc/config/yaml.yml Normal file
عرض الملف

@@ -0,0 +1,203 @@
version: 2
postal:
# The hostname that the Postal web interface runs on
web_hostname: postal.example.com
# The HTTP protocol to use for the Postal web interface
web_protocol: https
# The hostname that the Postal SMTP server runs on
smtp_hostname: postal.example.com
# Should IP pools be enabled for this installation?
use_ip_pools: false
# The maximum number of delivery attempts
default_maximum_delivery_attempts: 18
# The number of days to hold a message before they will be expired
default_maximum_hold_expiry_days: 7
# The number of days an address will remain in a suppression list before being removed
default_suppression_list_automatic_removal_days: 30
# The default threshold at which a message should be treated as spam
default_spam_threshold: 5
# The default threshold at which a message should be treated as spam failure
default_spam_failure_threshold: 20
# Domain verification and checking usually checks with a domain's nameserver. Enable this to check with the server's local nameservers.
use_local_ns_for_domain_verification: false
# Append a Resend-Sender header to all outgoing e-mails
use_resent_sender_header: true
# Path to the private key used for signing
signing_key_path: config/postal/signing.key
# An array of SMTP relays in the format of smtp://host:port
smtp_relays: []
web_server:
# The default port the web server should listen on unless overriden by the PORT environment variable
default_port: 5000
# The default bind address the web server should listen on unless overriden by the BIND_ADDRESS environment variable
default_bind_address: 127.0.0.1
# The maximum number of threads which can be used by the web server
max_threads: 5
worker:
# The default port for the worker health server to listen on
default_health_server_port: 9090
# The default bind address for the worker health server to listen on
default_health_server_bind_address: 127.0.0.1
main_db:
# Hostname for the main MariaDB server
host: localhost
# The MariaDB port to connect to
port: 3306
# The MariaDB username
username: postal
# The MariaDB password
password:
# The MariaDB database name
database: postal
# The maximum size of the MariaDB connection pool
pool_size: 5
# The encoding to use when connecting to the MariaDB database
encoding: utf8mb4
message_db:
# Hostname for the MariaDB server which stores the mail server databases
host: localhost
# The MariaDB port to connect to
port: 3306
# The MariaDB username
username: postal
# The MariaDB password
password:
# The MariaDB prefix to add to database names
database_name_prefix: postal
logging:
# Enable the default Rails logger
rails_log_enabled: false
# A DSN which should be used to report exceptions to Sentry
sentry_dsn:
# Enable the Postal logger to log to STDOUT
enabled: true
# Enable highlighting of log lines
highlighting_enabled: false
gelf:
# GELF-capable host to send logs to
host:
# GELF port to send logs to
port: 12201
# The facility name to add to all log entries sent to GELF
facility: postal
smtp_server:
# The default port the SMTP server should listen on unless overriden by the PORT environment variable
default_port: 25
# The default bind address the SMTP server should listen on unless overriden by the BIND_ADDRESS environment variable
default_bind_address: 127.0.0.1
# The default port for the SMTP server health server to listen on
default_health_server_port: 9091
# The default bind address for the SMTP server health server to listen on
default_health_server_bind_address: 127.0.0.1
# Enable TLS for the SMTP server (requires certificate)
tls_enabled: false
# The path to the SMTP server's TLS certificate
tls_certificate_path: config/postal/smtp.cert
# The path to the SMTP server's TLS private key
tls_private_key_path: config/postal/smtp.key
# Override ciphers to use for SSL
tls_ciphers:
# The SSL versions which are supported
ssl_version: SSLv23
# Enable proxy protocol for use behind some load balancers
proxy_protocol: false
# Enable connection logging
log_connections: false
# The maximum message size to accept from the SMTP server (in MB)
max_message_size: 14
# A regular expression to use to exclude connections from logging
log_ip_address_exclusion_matcher:
dns:
# The names of the default MX records
mx_records:
- mx1.postal.example.com
- mx2.postal.example.com
# The location of the SPF record
spf_include: spf.postal.example.com
# The return path hostname
return_path_domain: rp.postal.example.com
# The domain to use for hosting route-specific addresses
route_domain: routes.postal.example.com
# The CNAME which tracking domains should be pointed to
track_domain: track.postal.example.com
# The hostname to use in HELO/EHLO when connecting to external SMTP servers
helo_hostname:
# The identifier to use for DKIM keys in DNS records
dkim_identifier: postal
# The prefix to add before TXT record verification string
domain_verify_prefix: postal-verification
# The domain to use on external domains which points to the Postal return path domain
custom_return_path_prefix: psrp
smtp:
# The hostname to send application-level e-mails to
host: 127.0.0.1
# The port number to send application-level e-mails to
port: 25
# The username to use when authentication to the SMTP server
username:
# The password to use when authentication to the SMTP server
password:
# The name to use as the from name outgoing emails from Postal
from_name: Postal
# The e-mail to use as the from address outgoing emails from Postal
from_address: postal@example.com
rails:
# The Rails environment to run the application in
environment: production
# The secret key used to sign and encrypt cookies and session data in the application
secret_key:
rspamd:
# Enable rspamd for message inspection
enabled: false
# The hostname of the rspamd server
host: 127.0.0.1
# The port of the rspamd server
port: 11334
# Enable SSL for the rspamd connection
ssl: false
# The password for the rspamd server
password:
# Any flags for the rspamd server
flags:
spamd:
# Enable SpamAssassin for message inspection
enabled: false
# The hostname for the SpamAssassin server
host: 127.0.0.1
# The port of the SpamAssassin server
port: 783
clamav:
# Enable ClamAV for message inspection
enabled: false
# The host of the ClamAV server
host: 127.0.0.1
# The port of the ClamAV server
port: 2000
smtp_client:
# The open timeout for outgoing SMTP connections
open_timeout: 30
# The read timeout for outgoing SMTP connections
read_timeout: 30
migration_waiter:
# Wait for all migrations to run before starting a process
enabled: false
# The number of attempts to try waiting for migrations to complete before start
attempts: 120
# The number of seconds to wait between each migration check
sleep_time: 2

عرض الملف

@@ -8,8 +8,14 @@ services:
volumes:
- "./docker/ci-config:/config"
environment:
POSTAL_CONFIG_ROOT: /config
KATAPULT_CONFIG_FILE: /ci-config.yml
POSTAL_SIGNING_KEY_PATH: /config/signing.key
MAIN_DB_HOST: mariadb
MAIN_DB_USERNAME: root
MESSAGE_DB_HOST: mariadb
MESSAGE_DB_USERNAME: root
LOGGING_ENABLED: "false"
RAILS_ENVIRONMENT: test
RAILS_LOG_ENABLED: "false"
WAIT_FOR_TIMEOUT: 90
WAIT_FOR_TARGETS: |-
mariadb:3306

عرض الملف

@@ -1,42 +0,0 @@
general:
use_ip_pools: false
web_server:
bind_address: 127.0.0.1
port: 5000
smtp_server:
port: 2525
main_db:
host: mariadb
username: root
password:
database: postal
message_db:
host: mariadb
username: root
password:
prefix: postal
dns:
mx_records:
- mx.postal.example.com
smtp_server_hostname: postal.example.com
spf_include: spf.postal.example.com
return_path: rp.postal.example.com
route_domain: routes.postal.example.com
track_domain: track.postal.example.com
smtp:
host: 127.0.0.1
port: 2525
username:
password:
from_name: Postal
from_address: postal@yourdomain.com
rails:
secret_key: 5e82ca222ccc8642fb38a8ea4b847408b983b42a497ec16b499ed89ba506a4edcab8ac015b32dbd1ea620119484e0c1dc056f97b4b37cd29146192df5ccd6abec345a0125c424daad12430a4796d108197c746f5eb8e9e1faa36a8ff8d0c5a9cf00191f997c354341fd656ca0ef9d7b40a37ea4b1b1e78fe9030fa9990dbb662
environment: test

عرض الملف

@@ -7,8 +7,8 @@
class MigrationWaiter
ATTEMPTS = ENV.fetch("MIGRATION_WAITER_ATTEMPTS", 120)
SLEEP_TIME = ENV.fetch("MIGRATION_WAITER_SLEEP_TIME", 2)
ATTEMPTS = Postal::Config.migration_waiter.attempts
SLEEP_TIME = Postal::Config.migration_waiter.sleep_time
class << self
@@ -35,7 +35,7 @@ class MigrationWaiter
def wait_if_appropriate
# Don't wait if not configured
return unless ENV.fetch("MIGRATION_WAITER_ENABLED", "false") == "true"
return unless Postal::Config.migration_waiter.enabled?
# Don't wait in the console, rake tasks or rails commands
return if console? || rake_task? || rails_command?

عرض الملف

@@ -6,190 +6,119 @@ require "pathname"
require "cgi"
require "openssl"
require "fileutils"
require "konfig"
require "konfig/sources/environment"
require "konfig/sources/yaml"
require "dotenv"
require "klogger"
require_relative "error"
require_relative "version"
require_relative "config_schema"
require_relative "legacy_config_source"
module Postal
# rubocop:disable Lint/EmptyClass
class Config
end
# rubocop:enable Lint/EmptyClass
class << self
def self.host
@host ||= config.web.host || "localhost:5000"
end
def self.protocol
@protocol ||= config.web.protocol || "http"
end
def self.host_with_protocol
@host_with_protocol ||= "#{protocol}://#{host}"
end
def self.app_root
@app_root ||= Pathname.new(File.expand_path("../..", __dir__))
end
def self.config
@config ||= begin
require "hashie/mash"
config = Hashie::Mash.new(defaults)
config = config.deep_merge(yaml_config)
config.deep_merge(local_yaml_config)
end
end
def self.config_root
if ENV["POSTAL_CONFIG_ROOT"]
@config_root ||= Pathname.new(ENV["POSTAL_CONFIG_ROOT"])
else
@config_root ||= Pathname.new(File.expand_path("../../config/postal", __dir__))
end
end
def self.config_file_path
if env == "default"
@config_file_path ||= File.join(config_root, "postal.yml")
else
@config_file_path ||= File.join(config_root, "postal.#{env}.yml")
end
end
def self.env
@env ||= ENV.fetch("POSTAL_ENV", "default")
end
def self.yaml_config
@yaml_config ||= File.exist?(config_file_path) ? YAML.load_file(config_file_path) : {}
end
def self.local_config_file_path
@local_config_file_path ||= File.join(config_root, "postal.local.yml")
end
def self.local_yaml_config
@local_yaml_config ||= File.exist?(local_config_file_path) ? YAML.load_file(local_config_file_path) : {}
end
def self.defaults_file_path
@defaults_file_path ||= app_root.join("config", "postal.defaults.yml")
end
def self.defaults
@defaults ||= begin
file = File.read(defaults_file_path)
yaml = ERB.new(file).result
YAML.safe_load(yaml)
end
end
# Return a generic logger for use generally throughout Postal.
# Return the path to the config file
#
# @return [Klogger::Logger] A logger instance
def self.logger
# @return [String]
def config_file_path
ENV.fetch("POSTAL_CONFIG_FILE_PATH", "config/postal/postal.yml")
end
def initialize_config
sources = []
# Load environment variables to begin with. Any config provided
# by an environment variable will override any provided in the
# config file.
Dotenv.load(".env")
sources << Konfig::Sources::Environment.new(ENV)
# If a config file exists, we need to load that. Config files can
# either be legacy (v1) or new (v2). Any file without a 'version'
# key is a legacy file whereas new-style config files will include
# the 'version: 2' key/value.
if File.file?(config_file_path)
puts "Loading config from #{config_file_path}"
config_file = File.read(config_file_path)
yaml = YAML.safe_load(config_file)
config_version = yaml["version"] || 1
case config_version
when 1
puts "WARNING: Using legacy config file format. Upgrade your postal.yml to use"
puts "version 2 of the Postal configuration or configure using environment"
puts "variables. See https://postalserver.io/config-v2 for details."
sources << LegacyConfigSource.new(yaml)
when 2
sources << Konfig::Sources::YAML.new(config_file)
else
raise "Invalid version specified in Postal config file. Must be 1 or 2."
end
else
puts "No configuration file found at #{config_file_path}"
puts "Only using environment variables for configuration"
end
# Build configuration with the provided sources.
Konfig::Config.build(ConfigSchema, sources: sources)
end
def host_with_protocol
@host_with_protocol ||= "#{Config.postal.web_protocol}://#{Config.postal.web_hostname}"
end
def logger
@logger ||= begin
k = Klogger.new(nil, destination: Rails.env.test? ? "/dev/null" : $stdout, highlight: Rails.env.development?)
k.add_destination(graylog_logging_destination) if config.logging&.graylog&.host.present?
k = Klogger.new(nil, destination: Config.logging.enabled? ? $stdout : "/dev/null", highlight: Config.logging.highlighting_enabled?)
k.add_destination(graylog_logging_destination) if Config.gelf.host.present?
k
end
end
def self.process_name
def process_name
@process_name ||= begin
string = "host:#{Socket.gethostname} pid:#{Process.pid}"
string += " procname:#{ENV['PROC_NAME']}" if ENV["PROC_NAME"]
string
"host:#{Socket.gethostname} pid:#{Process.pid}"
rescue StandardError
"pid:#{Process.pid}"
end
end
def self.locker_name
def locker_name
string = process_name.dup
string += " job:#{Thread.current[:job_id]}" if Thread.current[:job_id]
string += " thread:#{Thread.current.native_thread_id}"
string
end
def self.locker_name_with_suffix(suffix)
def locker_name_with_suffix(suffix)
"#{locker_name} #{suffix}"
end
def self.smtp_from_name
config.smtp&.from_name || "Postal"
def signing_key
@signing_key ||= OpenSSL::PKey::RSA.new(File.read(Config.postal.signing_key_path))
end
def self.smtp_from_address
config.smtp&.from_address || "postal@example.com"
end
def self.smtp_private_key_path
config.smtp_server.tls_private_key_path || config_root.join("smtp.key")
end
def self.smtp_private_key
@smtp_private_key ||= OpenSSL::PKey.read(File.read(smtp_private_key_path))
end
def self.smtp_certificate_path
config.smtp_server.tls_certificate_path || config_root.join("smtp.cert")
end
def self.smtp_certificate_data
@smtp_certificate_data ||= File.read(smtp_certificate_path)
end
def self.smtp_certificates
@smtp_certificates ||= begin
certs = smtp_certificate_data.scan(/-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/m)
certs.map do |c|
OpenSSL::X509::Certificate.new(c)
end.freeze
end
end
def self.signing_key_path
ENV.fetch("POSTAL_SIGNING_KEY_PATH") { config_root.join("signing.key") }
end
def self.signing_key
@signing_key ||= OpenSSL::PKey::RSA.new(File.read(signing_key_path))
end
def self.rp_dkim_dns_record
def rp_dkim_dns_record
public_key = signing_key.public_key.to_s.gsub(/-+[A-Z ]+-+\n/, "").gsub(/\n/, "")
"v=DKIM1; t=s; h=sha256; p=#{public_key};"
end
class ConfigError < Postal::Error
def ip_pools?
Config.postal.use_ip_pools?
end
def self.check_config!
return if ENV["POSTAL_SKIP_CONFIG_CHECK"].to_i == 1
unless File.exist?(config_file_path)
raise ConfigError, "No config found at #{config_file_path}"
end
return if File.exist?(signing_key_path)
raise ConfigError, "No signing key found at #{signing_key_path}"
end
def self.ip_pools?
config.general.use_ip_pools?
end
def self.graylog_logging_destination
def graylog_logging_destination
@graylog_logging_destination ||= begin
notifier = GELF::Notifier.new(config.logging.graylog.host, config.logging.graylog.port, "WAN")
notifier = GELF::Notifier.new(Config.gelf.host, Config.gelf.port, "WAN")
proc do |_logger, payload, group_ids|
short_message = payload.delete(:message) || "[message missing]"
notifier.notify!(short_message: short_message, **{
facility: config.logging.graylog.facility,
_environment: Rails.env.to_s,
facility: Config.gelf.facility,
_environment: Config.rails.environment,
_version: Postal::VERSION.to_s,
_group_ids: group_ids.join(" ")
}.merge(payload.transform_keys { |k| "_#{k}".to_sym }.transform_values(&:to_s)))
@@ -198,3 +127,7 @@ module Postal
end
end
Config = initialize_config
end

464
lib/postal/config_schema.rb Normal file
عرض الملف

@@ -0,0 +1,464 @@
# frozen_string_literal: true
require "uri"
module Postal
# REMEMBER: If you change the schema, remember to regenerate the configuration docs
# using the rake command below:
#
# rake postal:generate_config_docs
ConfigSchema = Konfig::Schema.draw do
group :postal do
string :web_hostname do
description "The hostname that the Postal web interface runs on"
default "postal.example.com"
end
string :web_protocol do
description "The HTTP protocol to use for the Postal web interface"
default "https"
end
string :smtp_hostname do
description "The hostname that the Postal SMTP server runs on"
default "postal.example.com"
end
boolean :use_ip_pools do
description "Should IP pools be enabled for this installation?"
default false
end
integer :default_maximum_delivery_attempts do
description "The maximum number of delivery attempts"
default 18
end
integer :default_maximum_hold_expiry_days do
description "The number of days to hold a message before they will be expired"
default 7
end
integer :default_suppression_list_automatic_removal_days do
description "The number of days an address will remain in a suppression list before being removed"
default 30
end
integer :default_spam_threshold do
description "The default threshold at which a message should be treated as spam"
default 5
end
integer :default_spam_failure_threshold do
description "The default threshold at which a message should be treated as spam failure"
default 20
end
boolean :use_local_ns_for_domain_verification do
description "Domain verification and checking usually checks with a domain's nameserver. Enable this to check with the server's local nameservers."
default false
end
boolean :use_resent_sender_header do
description "Append a Resend-Sender header to all outgoing e-mails"
default true
end
string :signing_key_path do
description "Path to the private key used for signing"
default "config/postal/signing.key"
end
string :smtp_relays do
array
description "An array of SMTP relays in the format of smtp://host:port"
transform do |value|
uri = URI.parse(value)
query = uri.query ? CGI.parse(uri.query) : {}
{
host: uri.host,
port: uri.port || 25,
ssl_mode: query["ssl_mode"]&.first || "Auto"
}
end
end
end
group :web_server do
integer :default_port do
description "The default port the web server should listen on unless overriden by the PORT environment variable"
default 5000
end
string :default_bind_address do
description "The default bind address the web server should listen on unless overriden by the BIND_ADDRESS environment variable"
default "127.0.0.1"
end
integer :max_threads do
description "The maximum number of threads which can be used by the web server"
default 5
end
end
group :worker do
integer :default_health_server_port do
description "The default port for the worker health server to listen on"
default 9090
end
string :default_health_server_bind_address do
description "The default bind address for the worker health server to listen on"
default "127.0.0.1"
end
end
group :main_db do
string :host do
description "Hostname for the main MariaDB server"
default "localhost"
end
integer :port do
description "The MariaDB port to connect to"
default 3306
end
string :username do
description "The MariaDB username"
default "postal"
end
string :password do
description "The MariaDB password"
end
string :database do
description "The MariaDB database name"
default "postal"
end
integer :pool_size do
description "The maximum size of the MariaDB connection pool"
default 5
end
string :encoding do
description "The encoding to use when connecting to the MariaDB database"
default "utf8mb4"
end
end
group :message_db do
string :host do
description "Hostname for the MariaDB server which stores the mail server databases"
default "localhost"
end
integer :port do
description "The MariaDB port to connect to"
default 3306
end
string :username do
description "The MariaDB username"
default "postal"
end
string :password do
description "The MariaDB password"
end
string :database_name_prefix do
description "The MariaDB prefix to add to database names"
default "postal"
end
end
group :logging do
boolean :rails_log_enabled do
description "Enable the default Rails logger"
default false
end
string :sentry_dsn do
description "A DSN which should be used to report exceptions to Sentry"
end
boolean :enabled do
description "Enable the Postal logger to log to STDOUT"
default true
end
boolean :highlighting_enabled do
description "Enable highlighting of log lines"
default false
end
end
group :gelf do
string :host do
description "GELF-capable host to send logs to"
end
integer :port do
description "GELF port to send logs to"
default 12_201
end
string :facility do
description "The facility name to add to all log entries sent to GELF"
default "postal"
end
end
group :smtp_server do
integer :default_port do
description "The default port the SMTP server should listen on unless overriden by the PORT environment variable"
default 25
end
string :default_bind_address do
description "The default bind address the SMTP server should listen on unless overriden by the BIND_ADDRESS environment variable"
default "127.0.0.1"
end
integer :default_health_server_port do
description "The default port for the SMTP server health server to listen on"
default 9091
end
string :default_health_server_bind_address do
description "The default bind address for the SMTP server health server to listen on"
default "127.0.0.1"
end
boolean :tls_enabled do
description "Enable TLS for the SMTP server (requires certificate)"
default false
end
string :tls_certificate_path do
description "The path to the SMTP server's TLS certificate"
default "config/postal/smtp.cert"
end
string :tls_private_key_path do
description "The path to the SMTP server's TLS private key"
default "config/postal/smtp.key"
end
string :tls_ciphers do
description "Override ciphers to use for SSL"
end
string :ssl_version do
description "The SSL versions which are supported"
default "SSLv23"
end
boolean :proxy_protocol do
description "Enable proxy protocol for use behind some load balancers"
default false
end
boolean :log_connections do
description "Enable connection logging"
default false
end
integer :max_message_size do
description "The maximum message size to accept from the SMTP server (in MB)"
default 14
end
string :log_ip_address_exclusion_matcher do
description "A regular expression to use to exclude connections from logging"
end
end
group :dns do
string :mx_records do
description "The names of the default MX records"
array
default ["mx1.postal.example.com", "mx2.postal.example.com"]
end
string :spf_include do
description "The location of the SPF record"
default "spf.postal.example.com"
end
string :return_path_domain do
description "The return path hostname"
default "rp.postal.example.com"
end
string :route_domain do
description "The domain to use for hosting route-specific addresses"
default "routes.postal.example.com"
end
string :track_domain do
description "The CNAME which tracking domains should be pointed to"
default "track.postal.example.com"
end
string :helo_hostname do
description "The hostname to use in HELO/EHLO when connecting to external SMTP servers"
end
string :dkim_identifier do
description "The identifier to use for DKIM keys in DNS records"
default "postal"
end
string :domain_verify_prefix do
description "The prefix to add before TXT record verification string"
default "postal-verification"
end
string :custom_return_path_prefix do
description "The domain to use on external domains which points to the Postal return path domain"
default "psrp"
end
end
group :smtp do
string :host do
description "The hostname to send application-level e-mails to"
default "127.0.0.1"
end
integer :port do
description "The port number to send application-level e-mails to"
default 25
end
string :username do
description "The username to use when authentication to the SMTP server"
end
string :password do
description "The password to use when authentication to the SMTP server"
end
string :from_name do
description "The name to use as the from name outgoing emails from Postal"
default "Postal"
end
string :from_address do
description "The e-mail to use as the from address outgoing emails from Postal"
default "postal@example.com"
end
end
group :rails do
string :environment do
description "The Rails environment to run the application in"
default "production"
end
string :secret_key do
description "The secret key used to sign and encrypt cookies and session data in the application"
end
end
group :rspamd do
boolean :enabled do
description "Enable rspamd for message inspection"
default false
end
string :host do
description "The hostname of the rspamd server"
default "127.0.0.1"
end
integer :port do
description "The port of the rspamd server"
default 11_334
end
boolean :ssl do
description "Enable SSL for the rspamd connection"
default false
end
string :password do
description "The password for the rspamd server"
end
string :flags do
description "Any flags for the rspamd server"
end
end
group :spamd do
boolean :enabled do
description "Enable SpamAssassin for message inspection"
default false
end
string :host do
description "The hostname for the SpamAssassin server"
default "127.0.0.1"
end
integer :port do
description "The port of the SpamAssassin server"
default 783
end
end
group :clamav do
boolean :enabled do
description "Enable ClamAV for message inspection"
default false
end
string :host do
description "The host of the ClamAV server"
default "127.0.0.1"
end
integer :port do
description "The port of the ClamAV server"
default 2000
end
end
group :smtp_client do
integer :open_timeout do
description "The open timeout for outgoing SMTP connections"
default 30
end
integer :read_timeout do
description "The read timeout for outgoing SMTP connections"
default 30
end
end
group :migration_waiter do
boolean :enabled do
description "Wait for all migrations to run before starting a process"
default false
end
integer :attempts do
description "The number of attempts to try waiting for migrations to complete before start"
default 120
end
integer :sleep_time do
description "The number of seconds to wait between each migration check"
default 2
end
end
end
end

عرض الملف

@@ -0,0 +1,122 @@
# frozen_string_literal: true
require "konfig/sources/abstract"
require "konfig/error"
module Postal
class LegacyConfigSource < Konfig::Sources::Abstract
# This maps all the new configuration values to where they
# exist in the old YAML file. The source will load any YAML
# file that has been provided to this source in order. A
# warning will be generated to the console for configuration
# loaded from this format.
MAPPING = {
"postal.web_hostname" => -> (c) { c.dig("web", "host") },
"postal.web_protocol" => -> (c) { c.dig("web", "protocol") },
"postal.smtp_hostname" => -> (c) { c.dig("dns", "smtp_server_hostname") },
"postal.use_ip_pools" => -> (c) { c.dig("general", "use_ip_pools") },
"logging.sentry_dsn" => -> (c) { c.dig("general", "exception_url") },
"postal.default_maximum_delivery_attempts" => -> (c) { c.dig("general", "maximum_delivery_attempts") },
"postal.default_maximum_hold_expiry_days" => -> (c) { c.dig("general", "maximum_hold_expiry_days") },
"postal.default_suppression_list_automatic_removal_days" => -> (c) { c.dig("general", "suppression_list_removal_delay") },
"postal.use_local_ns_for_domain_verification" => -> (c) { c.dig("general", "use_local_ns_for_domains") },
"postal.default_spam_threshold" => -> (c) { c.dig("general", "default_spam_threshold") },
"postal.default_spam_failure_threshold" => -> (c) { c.dig("general", "default_spam_failure_threshold") },
"postal.use_resent_sender_header" => -> (c) { c.dig("general", "use_resent_sender_header") },
# SMTP relays must be converted to the new URI style format and they'll
# then be transformed back to a hash by the schema transform.
"postal.smtp_relays" => -> (c) { c["smtp_relays"]&.map { |r| "smtp://#{r['hostname']}:#{r['port']}?ssl_mode=#{r['ssl_mode']}" } },
"web_server.default_bind_address" => -> (c) { c.dig("web_server", "bind_address") },
"web_server.default_port" => -> (c) { c.dig("web_server", "port") },
"web_server.max_threads" => -> (c) { c.dig("web_server", "max_threads") },
"main_db.host" => -> (c) { c.dig("main_db", "host") },
"main_db.port" => -> (c) { c.dig("main_db", "port") },
"main_db.username" => -> (c) { c.dig("main_db", "username") },
"main_db.password" => -> (c) { c.dig("main_db", "password") },
"main_db.database" => -> (c) { c.dig("main_db", "database") },
"main_db.pool_size" => -> (c) { c.dig("main_db", "pool_size") },
"main_db.encoding" => -> (c) { c.dig("main_db", "encoding") },
"message_db.host" => -> (c) { c.dig("message_db", "host") },
"message_db.port" => -> (c) { c.dig("message_db", "port") },
"message_db.username" => -> (c) { c.dig("message_db", "username") },
"message_db.password" => -> (c) { c.dig("message_db", "password") },
"message_db.database_name_prefix" => -> (c) { c.dig("message_db", "prefix") },
"logging.rails_log_enabled" => -> (c) { c.dig("logging", "rails_log") },
"gelf.host" => -> (c) { c.dig("logging", "graylog", "host") },
"gelf.port" => -> (c) { c.dig("logging", "graylog", "port") },
"gelf.facility" => -> (c) { c.dig("logging", "graylog", "facility") },
"smtp_server.default_port" => -> (c) { c.dig("smtp_server", "port") },
"smtp_server.default_bind_address" => -> (c) { c.dig("smtp_server", "bind_address") },
"smtp_server.tls_enabled" => -> (c) { c.dig("smtp_server", "tls_enabled") },
"smtp_server.tls_certificate_path" => -> (c) { c.dig("smtp_server", "tls_certificate_path") },
"smtp_server.tls_private_key_path" => -> (c) { c.dig("smtp_server", "tls_private_key_path") },
"smtp_server.tls_ciphers" => -> (c) { c.dig("smtp_server", "tls_ciphers") },
"smtp_server.ssl_version" => -> (c) { c.dig("smtp_server", "ssl_version") },
"smtp_server.proxy_protocol" => -> (c) { c.dig("smtp_server", "proxy_protocol") },
"smtp_server.log_connections" => -> (c) { c.dig("smtp_server", "log_connect") },
"smtp_server.max_message_size" => -> (c) { c.dig("smtp_server", "max_message_size") },
"dns.mx_records" => -> (c) { c.dig("dns", "mx_records") },
"dns.spf_include" => -> (c) { c.dig("dns", "spf_include") },
"dns.return_path_domain" => -> (c) { c.dig("dns", "return_path") },
"dns.route_domain" => -> (c) { c.dig("dns", "route_domain") },
"dns.track_domain" => -> (c) { c.dig("dns", "track_domain") },
"dns.helo_hostname" => -> (c) { c.dig("dns", "helo_hostname") },
"dns.dkim_identifier" => -> (c) { c.dig("dns", "dkim_identifier") },
"dns.domain_verify_prefix" => -> (c) { c.dig("dns", "domain_verify_prefix") },
"dns.custom_return_path_prefix" => -> (c) { c.dig("dns", "custom_return_path_prefix") },
"smtp.host" => -> (c) { c.dig("smtp", "host") },
"smtp.port" => -> (c) { c.dig("smtp", "port") },
"smtp.username" => -> (c) { c.dig("smtp", "username") },
"smtp.password" => -> (c) { c.dig("smtp", "password") },
"smtp.from_name" => -> (c) { c.dig("smtp", "from_name") },
"smtp.from_address" => -> (c) { c.dig("smtp", "from_address") },
"rails.environment" => -> (c) { c.dig("rails", "environment") },
"rails.secret_key" => -> (c) { c.dig("rails", "secret_key") },
"rspamd.enabled" => -> (c) { c.dig("rspamd", "enabled") },
"rspamd.host" => -> (c) { c.dig("rspamd", "host") },
"rspamd.port" => -> (c) { c.dig("rspamd", "port") },
"rspamd.ssl" => -> (c) { c.dig("rspamd", "ssl") },
"rspamd.password" => -> (c) { c.dig("rspamd", "password") },
"rspamd.flags" => -> (c) { c.dig("rspamd", "flags") },
"spamd.enabled" => -> (c) { c.dig("spamd", "enabled") },
"spamd.host" => -> (c) { c.dig("spamd", "host") },
"spamd.port" => -> (c) { c.dig("spamd", "port") },
"clamav.enabled" => -> (c) { c.dig("clamav", "enabled") },
"clamav.host" => -> (c) { c.dig("clamav", "host") },
"clamav.port" => -> (c) { c.dig("clamav", "port") },
"smtp_client.open_timeout" => -> (c) { c.dig("smtp_client", "open_timeout") },
"smtp_client.read_timeout" => -> (c) { c.dig("smtp_client", "read_timeout") }
}.freeze
def initialize(config)
super()
@config = config
end
def get(path, attribute: nil)
path_string = path.join(".")
raise Konfig::ValueNotPresentError unless MAPPING.key?(path_string)
legacy_value = MAPPING[path_string].call(@config)
raise Konfig::ValueNotPresentError if legacy_value.nil?
legacy_value
end
end
end

عرض الملف

@@ -63,11 +63,11 @@ module Postal
def establish_connection
Mysql2::Client.new(
host: Postal.config.message_db.host,
username: Postal.config.message_db.username,
password: Postal.config.message_db.password,
port: Postal.config.message_db.port,
encoding: Postal.config.message_db.encoding || "utf8mb4"
host: Postal::Config.message_db.host,
username: Postal::Config.message_db.username,
password: Postal::Config.message_db.password,
port: Postal::Config.message_db.port,
encoding: Postal::Config.message_db.encoding
)
end

عرض الملف

@@ -272,7 +272,7 @@ module Postal
# Return the correct database name
#
def database_name
@database_name ||= "#{Postal.config.message_db.prefix}-server-#{@server_id}"
@database_name ||= "#{Postal::Config.message_db.database_name_prefix}-server-#{@server_id}"
end
#

عرض الملف

@@ -128,7 +128,7 @@ module Postal
#
def create_delivery(status, options = {})
delivery = Delivery.create(self, options.merge(status: status))
hold_expiry = status == "Held" ? Postal.config.general.maximum_hold_expiry_days.days.from_now.to_f : nil
hold_expiry = status == "Held" ? Postal::Config.postal.default_maximum_hold_expiry_days.days.from_now.to_f : nil
update(status: status, last_delivery_attempt: delivery.timestamp.to_f, held: status == "Held", hold_expiry: hold_expiry)
delivery
end
@@ -511,7 +511,7 @@ module Postal
# Was thsi message sent to a return path?
#
def rcpt_to_return_path?
!!(rcpt_to =~ /@#{Regexp.escape(Postal.config.dns.custom_return_path_prefix)}\./)
!!(rcpt_to =~ /@#{Regexp.escape(Postal::Config.dns.custom_return_path_prefix)}\./)
end
#

عرض الملف

@@ -9,7 +9,7 @@ module Postal
end
def add(type, address, options = {})
keep_until = (options[:days] || Postal.config.general.suppression_list_removal_delay).days.from_now.to_f
keep_until = (options[:days] || Postal::Config.postal.default_suppression_list_automatic_removal_days).days.from_now.to_f
if existing = @database.select("suppressions", where: { type: type, address: address }, limit: 1).first
reason = options[:reason] || existing["reason"]
@database.update("suppressions", { reason: reason, keep_until: keep_until }, where: { id: existing["id"] })

عرض الملف

@@ -24,14 +24,14 @@ module Postal
# installation.
def inspectors
[].tap do |inspectors|
if Postal.config.rspamd&.enabled
inspectors << MessageInspectors::Rspamd.new(Postal.config.rspamd)
elsif Postal.config.spamd&.enabled
inspectors << MessageInspectors::SpamAssassin.new(Postal.config.spamd)
if Postal::Config.rspamd.enabled?
inspectors << MessageInspectors::Rspamd.new(Postal::Config.rspamd)
elsif Postal::Config.spamd.enabled?
inspectors << MessageInspectors::SpamAssassin.new(Postal::Config.spamd)
end
if Postal.config.clamav&.enabled
inspectors << MessageInspectors::Clamav.new(Postal.config.clamav)
if Postal::Config.clamav.enabled?
inspectors << MessageInspectors::Clamav.new(Postal::Config.clamav)
end
end
end

عرض الملف

@@ -0,0 +1,37 @@
# frozen_string_literal: true
require "konfig/exporters/abstract"
module Postal
class YamlConfigExporter < Konfig::Exporters::Abstract
def export
contents = []
contents << "version: 2"
contents << ""
@schema.groups.each do |group_name, group|
contents << "#{group_name}:"
group.attributes.each do |name, attr|
contents << " # #{attr.description}"
if attr.array?
if attr.default.blank?
contents << " #{name}: []"
else
contents << " #{name}:"
attr.default.each do |d|
contents << " - #{d}"
end
end
else
contents << " #{name}: #{attr.default}"
end
end
contents << ""
end
contents.join("\n")
end
end
end

عرض الملف

@@ -10,6 +10,18 @@ namespace :postal do
server.message_db.provisioner.migrate
end
end
desc "Generate configuration documentation"
task generate_config_docs: :environment do
require "konfig/exporters/env_vars_as_markdown"
FileUtils.mkdir_p("doc/config")
output = Konfig::Exporters::EnvVarsAsMarkdown.new(Postal::ConfigSchema).export
File.write("doc/config/environment-variables.md", output)
output = Postal::YamlConfigExporter.new(Postal::ConfigSchema).export
File.write("doc/config/yaml.yml", output)
end
end
Rake::Task["db:migrate"].enhance do

عرض الملف

@@ -1,24 +0,0 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require File.expand_path("../lib/postal/config", __dir__)
require "openssl"
require "securerandom"
require "fileutils"
unless File.directory?(Postal.config_root)
FileUtils.mkdir_p(Postal.config_root)
end
unless File.exist?(Postal.config_file_path)
content = File.read(Postal.app_root.join("config", "postal.example.yml"))
content.gsub!("{{secretkey}}", SecureRandom.hex(128))
File.write(Postal.config_file_path, content)
puts "Created example config file at #{Postal.config_file_path}"
end
unless File.exist?(Postal.signing_key_path)
key = OpenSSL::PKey::RSA.new(1024).to_s
File.write(Postal.signing_key_path, key)
puts "Created new signing key for DKIM & HTTP requests"
end

عرض الملف

@@ -3,21 +3,24 @@
require File.expand_path("../lib/postal/config", __dir__)
require "openssl"
unless File.exist?(Postal.smtp_private_key_path)
key_path = Postal::Config.smtp_server.tls_private_key_path
cert_path = Postal::Config.smtp_server.tls_certificate_path
unless File.exist?(key_path)
key = OpenSSL::PKey::RSA.new(2048).to_s
File.write(Postal.smtp_private_key_path, key)
puts "Created new private key for encrypting SMTP connections"
File.write(key_path, key)
puts "Created new private key for encrypting SMTP connections at #{key_path}"
end
unless File.exist?(Postal.smtp_certificate_path)
unless File.exist?(cert_path)
cert = OpenSSL::X509::Certificate.new
cert.subject = cert.issuer = OpenSSL::X509::Name.parse("/C=GB/O=Test/OU=Test/CN=Test")
cert.not_before = Time.now
cert.not_after = Time.now + (365 * 24 * 60 * 60)
cert.public_key = Postal.smtp_private_key.public_key
cert.public_key = SMTPServer::Server.tls_private_key.public_key
cert.serial = 0x0
cert.version = 2
cert.sign Postal.smtp_private_key, OpenSSL::Digest.new("SHA256")
File.write(Postal.smtp_certificate_path, cert.to_pem)
puts "Created new self signed certificate for encrypting SMTP connections"
cert.sign SMTPServer::Server.tls_private_key, OpenSSL::Digest.new("SHA256")
File.write(cert_path, cert.to_pem)
puts "Created new self signed certificate for encrypting SMTP connections at #{cert_path}"
end

عرض الملف

@@ -28,7 +28,7 @@ end
message = server.message_db.new_message
message.scope = "incoming"
message.rcpt_to = "#{server.token}@#{Postal.config.dns.return_path}"
message.rcpt_to = "#{server.token}@#{Postal::Config.dns.return_path_domain}"
message.mail_from = "MAILER-DAEMON@smtp.infra.atech.io"
message.raw_message = template
message.bounce = true

عرض الملف

@@ -5,11 +5,11 @@ require_relative "../lib/postal/config"
require "mysql2"
client = Mysql2::Client.new(
host: Postal.config.main_db.host,
username: Postal.config.main_db.username,
password: Postal.config.main_db.password,
port: Postal.config.main_db.port,
database: Postal.config.main_db.database
host: Postal::Config.main_db.host,
username: Postal::Config.main_db.username,
password: Postal::Config.main_db.password,
port: Postal::Config.main_db.port,
database: Postal::Config.main_db.database
)
result = client.query("SELECT COUNT(id) as size FROM `queued_messages` WHERE retry_after IS NULL OR " \
"retry_after <= ADDTIME(UTC_TIMESTAMP(), '30') AND locked_at IS NULL")

عرض الملف

@@ -5,5 +5,9 @@ $stderr.sync = true
require_relative "../config/environment"
HealthServer.start(name: "smtp-server", default_port: 9091)
HealthServer.start(
name: "smtp-server",
default_port: Postal::Config.smtp_server.default_health_server_port,
default_bind_address: Postal::Config.smtp_server.default_health_server_bind_address
)
SMTPServer::Server.new(debug: true).run

عرض الملف

@@ -25,9 +25,9 @@ rescue StandardError => e
puts "\e[31mMessage was not delivered successfully to SMTP server.\e[0m"
puts "Error: #{e.class} (#{e.message})"
puts
puts " SMTP Host: #{Postal.config.smtp.host}"
puts " SMTP Port: #{Postal.config.smtp.port}"
puts " SMTP Username: #{Postal.config.smtp.username}"
puts " SMTP Password: #{Postal.config.smtp.password}"
puts " SMTP Host: #{Postal::Config.smtp.host}"
puts " SMTP Port: #{Postal::Config.smtp.port}"
puts " SMTP Username: #{Postal::Config.smtp.username}"
puts " SMTP Password: #{Postal::Config.smtp.password}"
puts
end

عرض الملف

@@ -6,5 +6,10 @@ $stderr.sync = true
require_relative "../config/environment"
HealthServer.start(name: "worker", default_port: 9090)
HealthServer.start(
name: "worker",
default_port: Postal::Config.worker.default_health_server_port,
default_bind_address: Postal::Config.worker.default_health_server_bind_address
)
Worker::Process.new.run

عرض الملف

@@ -0,0 +1,118 @@
# This a legacy configuration file format which was used in Postal version
# less than v3. It remains supported in v3+ by mapping these values to their
# correct values. Support for this file format will be removed in Postal v4.
#
# It exists here for reference but also to faciliate testing to ensure the
# legacy mapping works as expected
version: 1
web:
host: postal.llamas.com
protocol: https
general:
use_ip_pools: false
exception_url: https://sentry.llamas.com/abcdef1234
maximum_delivery_attempts: 20
maximum_hold_expiry_days: 10
suppression_list_removal_delay: 60
use_local_ns_for_domains: true
default_spam_threshold: 10
default_spam_failure_threshold: 25
use_resent_sender_header: true
web_server:
bind_address: 127.0.0.1
port: 6000
max_threads: 10
main_db:
host: localhost
port: 3306
username: postal
password: t35tpassword
database: postal
pool_size: 20
encoding: utf8mb4
message_db:
host: localhost
port: 3306
username: postal
password: p05t41
prefix: postal
logging:
rails_log: true
graylog:
host: logs.llamas.com
port: 12201
facility: mailer
smtp_server:
port: 25
bind_address: 127.0.0.1
tls_enabled: true
tls_certificate_path: config/smtp.cert
tls_private_key_path: config/smtp.key
tls_ciphers: abc
ssl_version: SSLv23
proxy_protocol: false
log_connect: true
max_message_size: 10
smtp_relays:
- hostname: 1.2.3.4
port: 25
ssl_mode: Auto
- hostname: 2.2.2.2
port: 2525
ssl_mode: None
dns:
mx_records:
- mx1.postal.llamas.com
- mx2.postal.llamas.com
smtp_server_hostname: smtp.postal.llamas.com
spf_include: spf.postal.llamas.com
return_path: rp.postal.llamas.com
route_domain: routes.postal.llamas.com
track_domain: track.postal.llamas.com
helo_hostname: helo.postal.llamas.com
dkim_identifier: postal
domain_verify_prefix: postal-verification
custom_return_path_prefix: psrp
smtp:
host: 127.0.0.1
port: 25
username: postalserver
password: llama
from_name: Postal
from_address: postal@llamas.com
rails:
environment: production
secret_key: abcdef123123123123123
rspamd:
enabled: true
host: rspamd.llamas.com
port: 11334
ssl: false
password: llama
flags: abc
spamd:
enabled: false
host: spamd.llamas.com
port: 783
clamav:
enabled: false
host: clamav.llamas.com
port: 2000
smtp_client:
open_timeout: 60
read_timeout: 120

عرض الملف

@@ -42,7 +42,7 @@ module MessageDequeuer
end
context "when the number of attempts is more than the maximum" do
let(:queued_message) { create(:queued_message, :locked, message: message, attempts: Postal.config.general.maximum_delivery_attempts + 1) }
let(:queued_message) { create(:queued_message, :locked, message: message, attempts: Postal::Config.postal.default_maximum_delivery_attempts + 1) }
it "logs" do
processor.process

عرض الملف

@@ -0,0 +1,346 @@
# frozen_string_literal: true
require "rails_helper"
module Postal
SOURCE_CONFIG = YAML.safe_load(File.read(Rails.root.join("spec/examples/full_legacy_config_file.yml")))
# Rather than actuall test the LegacyConfigSource directly, I have decided
# to test this source via. the Konfig::Config system to ensure it works as
# expected in practice rather than just in theory. Testing '#get' would be
# fairly easy (and mostly pointless) where as testing the values we actually
# want are correct is preferred.
RSpec.describe LegacyConfigSource do
before do
# For the purposes of testing, we want to ignore any defaults provided
# by the schema itself. Otherwise, we might see a value returned that
# looks correct but is actually the default rather than the value from
# config file.
allow_any_instance_of(Konfig::SchemaAttribute).to receive(:default).and_return(nil)
end
let(:source) { described_class.new(SOURCE_CONFIG) }
subject(:config) { Konfig::Config.build(ConfigSchema, sources: [source]) }
describe "the 'postal' group" do
it "returns a value for postal.web_hostname" do
expect(config.postal.web_hostname).to eq "postal.llamas.com"
end
it "returns a value for postal.web_protocol" do
expect(config.postal.web_protocol).to eq "https"
end
it "returns a value for postal.smtp_hostname" do
expect(config.postal.smtp_hostname).to eq "smtp.postal.llamas.com"
end
it "returns a value for postal.use_ip_pools?" do
expect(config.postal.use_ip_pools?).to eq false
end
it "returns a value for postal.default_maximum_delivery_attempts" do
expect(config.postal.default_maximum_delivery_attempts).to eq 20
end
it "returns a value for postal.default_maximum_hold_expiry_days" do
expect(config.postal.default_maximum_hold_expiry_days).to eq 10
end
it "returns a value for postal.default_suppression_list_automatic_removal_days" do
expect(config.postal.default_suppression_list_automatic_removal_days).to eq 60
end
it "returns a value for postal.use_local_ns_for_domain_verification?" do
expect(config.postal.use_local_ns_for_domain_verification?).to eq true
end
it "returns a value for postal.default_spam_threshold" do
expect(config.postal.default_spam_threshold).to eq 10
end
it "returns a value for postal.default_spam_failure_threshold" do
expect(config.postal.default_spam_failure_threshold).to eq 25
end
it "returns a value for postal.use_resent_sender_header?" do
expect(config.postal.use_resent_sender_header?).to eq true
end
it "returns a value for postal.smtp_relays" do
expect(config.postal.smtp_relays).to eq [
{ "host" => "1.2.3.4", "port" => 25, "ssl_mode" => "Auto" },
{ "host" => "2.2.2.2", "port" => 2525, "ssl_mode" => "None" }
]
end
end
describe "the 'web_server' group" do
it "returns a value for web_server.default_bind_address" do
expect(config.web_server.default_bind_address).to eq "127.0.0.1"
end
it "returns a value for web_server.default_port" do
expect(config.web_server.default_port).to eq 6000
end
it "returns a value for web_server.max_threads" do
expect(config.web_server.max_threads).to eq 10
end
end
describe "the 'main_db' group" do
it "returns a value for main_db.host" do
expect(config.main_db.host).to eq "localhost"
end
it "returns a value for main_db.port" do
expect(config.main_db.port).to eq 3306
end
it "returns a value for main_db.username" do
expect(config.main_db.username).to eq "postal"
end
it "returns a value for main_db.password" do
expect(config.main_db.password).to eq "t35tpassword"
end
it "returns a value for main_db.database" do
expect(config.main_db.database).to eq "postal"
end
it "returns a value for main_db.pool_size" do
expect(config.main_db.pool_size).to eq 20
end
it "returns a value for main_db.encoding" do
expect(config.main_db.encoding).to eq "utf8mb4"
end
end
describe "the 'message_db' group" do
it "returns a value for message_db.host" do
expect(config.message_db.host).to eq "localhost"
end
it "returns a value for message_db.port" do
expect(config.message_db.port).to eq 3306
end
it "returns a value for message_db.username" do
expect(config.message_db.username).to eq "postal"
end
it "returns a value for message_db.password" do
expect(config.message_db.password).to eq "p05t41"
end
it "returns a value for message_db.database_name_prefix" do
expect(config.message_db.database_name_prefix).to eq "postal"
end
end
describe "the 'logging' group" do
it "returns a value for logging.rails_log_enabled" do
expect(config.logging.rails_log_enabled).to eq true
end
end
describe "the 'gelf' group" do
it "returns a value for gelf.host" do
expect(config.gelf.host).to eq "logs.llamas.com"
end
it "returns a value for gelf.port" do
expect(config.gelf.port).to eq 12_201
end
it "returns a value for gelf.facility" do
expect(config.gelf.facility).to eq "mailer"
end
end
describe "the 'smtp_server' group" do
it "returns a value for smtp_server.default_port" do
expect(config.smtp_server.default_port).to eq 25
end
it "returns a value for smtp_server.default_bind_address" do
expect(config.smtp_server.default_bind_address).to eq "127.0.0.1"
end
it "returns a value for smtp_server.tls_enabled" do
expect(config.smtp_server.tls_enabled).to eq true
end
it "returns a value for smtp_server.tls_certificate_path" do
expect(config.smtp_server.tls_certificate_path).to eq "config/smtp.cert"
end
it "returns a value for smtp_server.tls_private_key_path" do
expect(config.smtp_server.tls_private_key_path).to eq "config/smtp.key"
end
it "returns a value for smtp_server.tls_ciphers" do
expect(config.smtp_server.tls_ciphers).to eq "abc"
end
it "returns a value for smtp_server.ssl_version" do
expect(config.smtp_server.ssl_version).to eq "SSLv23"
end
it "returns a value for smtp_server.proxy_protocol" do
expect(config.smtp_server.proxy_protocol).to eq false
end
it "returns a value for smtp_server.log_connections" do
expect(config.smtp_server.log_connections).to eq true
end
it "returns a value for smtp_server.max_message_size" do
expect(config.smtp_server.max_message_size).to eq 10
end
end
describe "the 'dns' group" do
it "returns a value for dns.mx_records" do
expect(config.dns.mx_records).to eq ["mx1.postal.llamas.com", "mx2.postal.llamas.com"]
end
it "returns a value for dns.spf_include" do
expect(config.dns.spf_include).to eq "spf.postal.llamas.com"
end
it "returns a value for dns.return_path_domain" do
expect(config.dns.return_path_domain).to eq "rp.postal.llamas.com"
end
it "returns a value for dns.route_domain" do
expect(config.dns.route_domain).to eq "routes.postal.llamas.com"
end
it "returns a value for dns.track_domain" do
expect(config.dns.track_domain).to eq "track.postal.llamas.com"
end
it "returns a value for dns.helo_hostname" do
expect(config.dns.helo_hostname).to eq "helo.postal.llamas.com"
end
it "returns a value for dns.dkim_identifier" do
expect(config.dns.dkim_identifier).to eq "postal"
end
it "returns a value for dns.domain_verify_prefix" do
expect(config.dns.domain_verify_prefix).to eq "postal-verification"
end
it "returns a value for dns.custom_return_path_prefix" do
expect(config.dns.custom_return_path_prefix).to eq "psrp"
end
end
describe "the 'smtp' group" do
it "returns a value for smtp.host" do
expect(config.smtp.host).to eq "127.0.0.1"
end
it "returns a value for smtp.port" do
expect(config.smtp.port).to eq 25
end
it "returns a value for smtp.username" do
expect(config.smtp.username).to eq "postalserver"
end
it "returns a value for smtp.password" do
expect(config.smtp.password).to eq "llama"
end
it "returns a value for smtp.from_name" do
expect(config.smtp.from_name).to eq "Postal"
end
it "returns a value for smtp.from_address" do
expect(config.smtp.from_address).to eq "postal@llamas.com"
end
end
describe "the 'rails' group" do
it "returns a value for rails.environment" do
expect(config.rails.environment).to eq "production"
end
it "returns a value for rails.secret_key" do
expect(config.rails.secret_key).to eq "abcdef123123123123123"
end
end
describe "the 'rspamd' group" do
it "returns a value for rspamd.enabled" do
expect(config.rspamd.enabled).to eq true
end
it "returns a value for rspamd.host" do
expect(config.rspamd.host).to eq "rspamd.llamas.com"
end
it "returns a value for rspamd.port" do
expect(config.rspamd.port).to eq 11_334
end
it "returns a value for rspamd.ssl?" do
expect(config.rspamd.ssl?).to eq false
end
it "returns a value for rspamd.password" do
expect(config.rspamd.password).to eq "llama"
end
it "returns a value for rspamd.flags" do
expect(config.rspamd.flags).to eq "abc"
end
end
describe "the 'spamd' group" do
it "returns a value for spamd.enabled" do
expect(config.spamd.enabled).to eq false
end
it "returns a value for spamd.host" do
expect(config.spamd.host).to eq "spamd.llamas.com"
end
it "returns a value for spamd.port" do
expect(config.spamd.port).to eq 783
end
end
describe "the 'clamav' group" do
it "returns a value for clamav.enabled" do
expect(config.clamav.enabled).to eq false
end
it "returns a value for clamav.host" do
expect(config.clamav.host).to eq "clamav.llamas.com"
end
it "returns a value for clamav.port" do
expect(config.clamav.port).to eq 2000
end
end
describe "the 'smtp_client' group" do
it "returns a value for smtp_client.open_timeout" do
expect(config.smtp_client.open_timeout).to eq 60
end
it "returns a value for smtp_client.read_timeout" do
expect(config.smtp_client.read_timeout).to eq 120
end
end
end
end

عرض الملف

@@ -12,7 +12,7 @@ describe ReceivedHeader do
it "returns the correct string" do
result = described_class.generate(nil, "testhelo", "1.1.1.1", :smtp)
expect(result).to eq "from testhelo (hostname.com [1.1.1.1]) " \
"by #{Postal.config.dns.smtp_server_hostname} " \
"by #{Postal::Config.postal.smtp_hostname} " \
"with SMTP; #{Time.now.utc.rfc2822}"
end
end
@@ -21,7 +21,7 @@ describe ReceivedHeader do
it "returns the correct string" do
server = Server.new(privacy_mode: true)
result = described_class.generate(server, "testhelo", "1.1.1.1", :smtp)
expect(result).to eq "by #{Postal.config.dns.smtp_server_hostname} " \
expect(result).to eq "by #{Postal::Config.postal.smtp_hostname} " \
"with SMTP; #{Time.now.utc.rfc2822}"
end
end
@@ -31,7 +31,7 @@ describe ReceivedHeader do
server = Server.new(privacy_mode: false)
result = described_class.generate(server, "testhelo", "1.1.1.1", :smtp)
expect(result).to eq "from testhelo (hostname.com [1.1.1.1]) " \
"by #{Postal.config.dns.smtp_server_hostname} " \
"by #{Postal::Config.postal.smtp_hostname} " \
"with SMTP; #{Time.now.utc.rfc2822}"
end
end
@@ -40,7 +40,7 @@ describe ReceivedHeader do
it "returns the correct string" do
result = described_class.generate(nil, "web-ui", "1.1.1.1", :http)
expect(result).to eq "from web-ui (hostname.com [1.1.1.1]) " \
"by #{Postal.config.web.host} " \
"by #{Postal::Config.postal.web_hostname} " \
"with HTTP; #{Time.now.utc.rfc2822}"
end
end

عرض الملف

@@ -39,7 +39,7 @@ module SMTPServer
client.handle("RCPT TO: #{route.name}@#{route.domain.name}")
Timecop.freeze do
client.handle("DATA")
expect(client.headers["received"]).to include "from test.example.com (1.2.3.4 [1.2.3.4]) by postal.example.com with SMTP; #{Time.now.utc.rfc2822}"
expect(client.headers["received"]).to include "from test.example.com (1.2.3.4 [1.2.3.4]) by #{Postal::Config.postal.smtp_hostname} with SMTP; #{Time.now.utc.rfc2822}"
end
end
@@ -72,7 +72,7 @@ module SMTPServer
client.handle("This is some content for the message.")
client.handle("It will keep going.")
expect(client.instance_variable_get("@data")).to eq <<~DATA
Received: from test.example.com (1.2.3.4 [1.2.3.4]) by #{Postal.config.dns.smtp_server_hostname} with SMTP; #{Time.now.utc.rfc2822}\r
Received: from test.example.com (1.2.3.4 [1.2.3.4]) by #{Postal::Config.postal.smtp_hostname} with SMTP; #{Time.now.utc.rfc2822}\r
Subject: Test\r
\r
This is some content for the message.\r

عرض الملف

@@ -24,7 +24,7 @@ module SMTPServer
describe "when finished sending data" do
context "when the data is larger than the maximum message size" do
it "returns an error and resets the state" do
allow(Postal.config.smtp_server).to receive(:max_message_size).and_return(1)
allow(Postal::Config.smtp_server).to receive(:max_message_size).and_return(1)
client.handle("DATA")
client.handle("a" * 1024 * 1024 * 10)
expect(client.handle(".")).to eq "552 Message too large (maximum size 1MB)"
@@ -34,10 +34,10 @@ module SMTPServer
context "when a loop is detected" do
it "returns an error and resets the state" do
client.handle("DATA")
client.handle("Received: from example1.com by #{Postal.config.dns.smtp_server_hostname}")
client.handle("Received: from example2.com by #{Postal.config.dns.smtp_server_hostname}")
client.handle("Received: from example1.com by #{Postal.config.dns.smtp_server_hostname}")
client.handle("Received: from example2.com by #{Postal.config.dns.smtp_server_hostname}")
client.handle("Received: from example1.com by #{Postal::Config.postal.smtp_hostname}")
client.handle("Received: from example2.com by #{Postal::Config.postal.smtp_hostname}")
client.handle("Received: from example1.com by #{Postal::Config.postal.smtp_hostname}")
client.handle("Received: from example2.com by #{Postal::Config.postal.smtp_hostname}")
client.handle("Subject: Test")
client.handle("From: #{mail_from}")
client.handle("To: #{rcpt_to}")
@@ -93,7 +93,7 @@ module SMTPServer
context "when sending a bounce message" do
let(:credential) { nil }
let(:rcpt_to) { "#{server.token}@#{Postal.config.dns.return_path}" }
let(:rcpt_to) { "#{server.token}@#{Postal::Config.dns.return_path_domain}" }
context "when there is a return path route" do
let(:domain) { create(:domain, owner: server) }
@@ -114,7 +114,7 @@ module SMTPServer
queued_message = QueuedMessage.first
expect(queued_message).to have_attributes(
domain: Postal.config.dns.return_path,
domain: Postal::Config.dns.return_path_domain,
server: server
)
@@ -145,7 +145,7 @@ module SMTPServer
queued_message = QueuedMessage.first
expect(queued_message).to have_attributes(
domain: Postal.config.dns.return_path,
domain: Postal::Config.dns.return_path_domain,
server: server
)

عرض الملف

@@ -11,7 +11,7 @@ module SMTPServer
describe "HELO" do
it "returns the hostname" do
expect(client.state).to eq :welcome
expect(client.handle("HELO: test.example.com")).to eq "250 #{Postal.config.dns.smtp_server_hostname}"
expect(client.handle("HELO: test.example.com")).to eq "250 #{Postal::Config.postal.smtp_hostname}"
expect(client.state).to eq :welcomed
end
end
@@ -24,7 +24,7 @@ module SMTPServer
context "when TLS is enabled" do
it "returns capabilities include starttls" do
allow(Postal.config.smtp_server).to receive(:tls_enabled?).and_return(true)
allow(Postal::Config.smtp_server).to receive(:tls_enabled?).and_return(true)
expect(client.handle("EHLO test.example.com")).to eq ["250-My capabilities are",
"250-STARTTLS",
"250 AUTH CRAM-MD5 PLAIN LOGIN"]

عرض الملف

@@ -36,18 +36,18 @@ module SMTPServer
context "when the RCPT TO address is the system return path host" do
it "returns an error if the server does not exist" do
expect(client.handle("RCPT TO: nothing@#{Postal.config.dns.return_path}")).to eq "550 Invalid server token"
expect(client.handle("RCPT TO: nothing@#{Postal::Config.dns.return_path_domain}")).to eq "550 Invalid server token"
end
it "returns an error if the server is suspended" do
server = create(:server, :suspended)
expect(client.handle("RCPT TO: #{server.token}@#{Postal.config.dns.return_path}"))
expect(client.handle("RCPT TO: #{server.token}@#{Postal::Config.dns.return_path_domain}"))
.to eq "535 Mail server has been suspended"
end
it "adds a recipient if all OK" do
server = create(:server)
address = "#{server.token}@#{Postal.config.dns.return_path}"
address = "#{server.token}@#{Postal::Config.dns.return_path_domain}"
expect(client.handle("RCPT TO: #{address}")).to eq "250 OK"
expect(client.recipients).to eq [[:bounce, address, server]]
expect(client.state).to eq :rcpt_to_received
@@ -56,19 +56,19 @@ module SMTPServer
context "when the RCPT TO address is on a host using the return path prefix" do
it "returns an error if the server does not exist" do
address = "nothing@#{Postal.config.dns.custom_return_path_prefix}.example.com"
address = "nothing@#{Postal::Config.dns.custom_return_path_prefix}.example.com"
expect(client.handle("RCPT TO: #{address}")).to eq "550 Invalid server token"
end
it "returns an error if the server is suspended" do
server = create(:server, :suspended)
address = "#{server.token}@#{Postal.config.dns.custom_return_path_prefix}.example.com"
address = "#{server.token}@#{Postal::Config.dns.custom_return_path_prefix}.example.com"
expect(client.handle("RCPT TO: #{address}")).to eq "535 Mail server has been suspended"
end
it "adds a recipient if all OK" do
server = create(:server)
address = "#{server.token}@#{Postal.config.dns.custom_return_path_prefix}.example.com"
address = "#{server.token}@#{Postal::Config.dns.custom_return_path_prefix}.example.com"
expect(client.handle("RCPT TO: #{address}")).to eq "250 OK"
expect(client.recipients).to eq [[:bounce, address, server]]
expect(client.state).to eq :rcpt_to_received
@@ -77,28 +77,28 @@ module SMTPServer
context "when the RCPT TO address is within the route domain" do
it "returns an error if the route token is invalid" do
address = "nothing@#{Postal.config.dns.route_domain}"
address = "nothing@#{Postal::Config.dns.route_domain}"
expect(client.handle("RCPT TO: #{address}")).to eq "550 Invalid route token"
end
it "returns an error if the server is suspended" do
server = create(:server, :suspended)
route = create(:route, server: server)
address = "#{route.token}@#{Postal.config.dns.route_domain}"
address = "#{route.token}@#{Postal::Config.dns.route_domain}"
expect(client.handle("RCPT TO: #{address}")).to eq "535 Mail server has been suspended"
end
it "returns an error if the route is set to Reject mail" do
server = create(:server)
route = create(:route, server: server, mode: "Reject")
address = "#{route.token}@#{Postal.config.dns.route_domain}"
address = "#{route.token}@#{Postal::Config.dns.route_domain}"
expect(client.handle("RCPT TO: #{address}")).to eq "550 Route does not accept incoming messages"
end
it "adds a recipient if all OK" do
server = create(:server)
route = create(:route, server: server)
address = "#{route.token}+tag1@#{Postal.config.dns.route_domain}"
address = "#{route.token}+tag1@#{Postal::Config.dns.route_domain}"
expect(client.handle("RCPT TO: #{address}")).to eq "250 OK"
expect(client.recipients).to eq [[:route, "#{route.name}+tag1@#{route.domain.name}", server, { route: route }]]
expect(client.state).to eq :rcpt_to_received

عرض الملف

@@ -216,7 +216,7 @@ describe Domain do
describe "#spf_record" do
it "returns the SPF record" do
expect(domain.spf_record).to eq "v=spf1 a mx include:#{Postal.config.dns.spf_include} ~all"
expect(domain.spf_record).to eq "v=spf1 a mx include:#{Postal::Config.dns.spf_include} ~all"
end
end
@@ -251,7 +251,7 @@ describe Domain do
end
it "returns the DKIM identifier" do
expect(domain.dkim_identifier).to eq "#{Postal.config.dns.dkim_identifier}-#{domain.dkim_identifier_string}"
expect(domain.dkim_identifier).to eq "#{Postal::Config.dns.dkim_identifier}-#{domain.dkim_identifier_string}"
end
end
end
@@ -269,14 +269,14 @@ describe Domain do
end
it "returns the DKIM identifier" do
expect(domain.dkim_record_name).to eq "#{Postal.config.dns.dkim_identifier}-#{domain.dkim_identifier_string}._domainkey"
expect(domain.dkim_record_name).to eq "#{Postal::Config.dns.dkim_identifier}-#{domain.dkim_identifier_string}._domainkey"
end
end
end
describe "#return_path_domain" do
it "returns the return path domain" do
expect(domain.return_path_domain).to eq "#{Postal.config.dns.custom_return_path_prefix}.#{domain.name}"
expect(domain.return_path_domain).to eq "#{Postal::Config.dns.custom_return_path_prefix}.#{domain.name}"
end
end
@@ -284,14 +284,14 @@ describe Domain do
let(:domain) { create(:domain, verification_method: "DNS") }
it "returns the DNS verification string" do
expect(domain.dns_verification_string).to eq "#{Postal.config.dns.domain_verify_prefix} #{domain.verification_token}"
expect(domain.dns_verification_string).to eq "#{Postal::Config.dns.domain_verify_prefix} #{domain.verification_token}"
end
end
describe "#resolver" do
context "when the local nameservers should be used" do
before do
allow(Postal.config.general).to receive(:use_local_ns_for_domains?).and_return(true)
allow(Postal::Config.postal).to receive(:use_local_ns_for_domain_verification?).and_return(true)
end
it "uses the local DNS" do

عرض الملف

@@ -1,6 +1,9 @@
# frozen_string_literal: true
ENV["POSTAL_ENV"] = "test"
ENV["POSTAL_CONFIG_FILE_PATH"] ||= "config/postal/postal.test.yml"
require "dotenv"
Dotenv.load(".env.test")
require File.expand_path("../config/environment", __dir__)
require "rspec/rails"