مراية لـ
https://github.com/postalserver/postal.git
تم المزامنة 2026-03-05 07:14:04 +00:00
Compare commits
13 الالتزامات
| المؤلف | SHA1 | التاريخ | |
|---|---|---|---|
|
|
ff901e99f7 | ||
|
|
ce19bf7988 | ||
|
|
8794a2f447 | ||
|
|
6b2bf9062d | ||
|
|
49cceaa6ca | ||
|
|
71f51db3c2 | ||
|
|
4a46f690de | ||
|
|
854aa5ebc8 | ||
|
|
990b575902 | ||
|
|
2bad645d98 | ||
|
|
41f6cf4d90 | ||
|
|
3fb40e4e24 | ||
|
|
0f9882f132 |
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
".": "2.1.6"
|
".": "2.2.0"
|
||||||
}
|
}
|
||||||
|
|||||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -2,6 +2,36 @@
|
|||||||
|
|
||||||
This file contains all the latest changes and updates to Postal.
|
This file contains all the latest changes and updates to Postal.
|
||||||
|
|
||||||
|
## [2.2.0](https://github.com/postalserver/postal/compare/2.1.6...2.2.0) (2024-02-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* load signing key path from POSTAL_SIGNING_KEY_PATH ([4a46f69](https://github.com/postalserver/postal/commit/4a46f690de3010f1ae4d6c17739530a4eae35c09))
|
||||||
|
* support for configuring postal with environment variables ([854aa5e](https://github.com/postalserver/postal/commit/854aa5ebc87de692b4691d48759aefd6fae9d133))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't use indifferent access for job params ([2bad645](https://github.com/postalserver/postal/commit/2bad645d980ad4b712a3c863b5350e4ee2895071)), closes [#2477](https://github.com/postalserver/postal/issues/2477) [#2714](https://github.com/postalserver/postal/issues/2714) [#2476](https://github.com/postalserver/postal/issues/2476) [#2500](https://github.com/postalserver/postal/issues/2500)
|
||||||
|
* extract x-postal-tag before holding ([6b2bf90](https://github.com/postalserver/postal/commit/6b2bf9062d662ede14617c4995ffaacca023a3b1)), closes [#2684](https://github.com/postalserver/postal/issues/2684)
|
||||||
|
* fixes error messages in web ui ([71f51db](https://github.com/postalserver/postal/commit/71f51db3c2515addaf8b280667555427d64796be))
|
||||||
|
* ignore message DB migrations in autoloader ([3fb40e4](https://github.com/postalserver/postal/commit/3fb40e4e247893b314e42affa4604a7a71a52c59))
|
||||||
|
* move tracking middleware before host authorization ([49cceaa](https://github.com/postalserver/postal/commit/49cceaa6ca862965448041279fc439ecba163ff8)), closes [#2415](https://github.com/postalserver/postal/issues/2415)
|
||||||
|
* use utc timestamps when determining raw table names ([ce19bf7](https://github.com/postalserver/postal/commit/ce19bf7988d522bf46aabf68090751427e286ffc))
|
||||||
|
|
||||||
|
|
||||||
|
### Miscellaneous Chores
|
||||||
|
|
||||||
|
* add binstubs for bundle and rspec ([41f6cf4](https://github.com/postalserver/postal/commit/41f6cf4d909518526af55ecb3fcccfa8fb8e1da2))
|
||||||
|
* add script to send html emails to a local SMTP server ([8794a2f](https://github.com/postalserver/postal/commit/8794a2f44783658a075a6f3985079ae4743412b1))
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* remove explicit autoload ([0f9882f](https://github.com/postalserver/postal/commit/0f9882f13204124df630606b1b9e36787c9c4011))
|
||||||
|
* remove Postal::Job.perform method ([990b575](https://github.com/postalserver/postal/commit/990b575902c45bb1678cc95f53ef3166c4b7092e))
|
||||||
|
|
||||||
## [2.1.6](https://github.com/postalserver/postal/compare/2.1.5...2.1.6) (2024-01-30)
|
## [2.1.6](https://github.com/postalserver/postal/compare/2.1.5...2.1.6) (2024-01-30)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class ApplicationController < ActionController::Base
|
|||||||
def render_form_errors(action_name, object)
|
def render_form_errors(action_name, object)
|
||||||
respond_to do |wants|
|
respond_to do |wants|
|
||||||
wants.html { render action_name }
|
wants.html { render action_name }
|
||||||
wants.json { render json: { form_errors: object.errors.full_messages }, status: :unprocessable_entity }
|
wants.json { render json: { form_errors: object.errors.map(&:full_message) }, status: :unprocessable_entity }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -292,6 +292,12 @@ class UnqueueMessageJob < Postal::Job
|
|||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Extract a tag and add it to the message if one doesn't exist
|
||||||
|
if queued_message.message.tag.nil? && tag = queued_message.message.headers["x-postal-tag"]
|
||||||
|
log "#{log_prefix} Added tag #{tag.last}"
|
||||||
|
queued_message.message.update(tag: tag.last)
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# If the credentials for this message is marked as holding and this isn't manual, hold it
|
# If the credentials for this message is marked as holding and this isn't manual, hold it
|
||||||
#
|
#
|
||||||
@@ -312,12 +318,6 @@ class UnqueueMessageJob < Postal::Job
|
|||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
# Extract a tag and add it to the message if one doesn't exist
|
|
||||||
if queued_message.message.tag.nil? && tag = queued_message.message.headers["x-postal-tag"]
|
|
||||||
log "#{log_prefix} Added tag #{tag.last}"
|
|
||||||
queued_message.message.update(tag: tag.last)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parse the content of the message as appropriate
|
# Parse the content of the message as appropriate
|
||||||
if queued_message.message.should_parse?
|
if queued_message.message.should_parse?
|
||||||
log "#{log_prefix} Parsing message content as it hasn't been parsed before"
|
log "#{log_prefix} Parsing message content as it hasn't been parsed before"
|
||||||
|
|||||||
110
bin/bundle
110
bin/bundle
@@ -1,3 +1,109 @@
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
# frozen_string_literal: true
|
||||||
load Gem.bin_path("bundler", "bundle")
|
|
||||||
|
#
|
||||||
|
# This file was generated by Bundler.
|
||||||
|
#
|
||||||
|
# The application 'bundle' is installed as part of a gem, and
|
||||||
|
# this file is here to facilitate running it.
|
||||||
|
#
|
||||||
|
|
||||||
|
require "rubygems"
|
||||||
|
|
||||||
|
m = Module.new do
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def invoked_as_script?
|
||||||
|
File.expand_path($0) == File.expand_path(__FILE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def env_var_version
|
||||||
|
ENV["BUNDLER_VERSION"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def cli_arg_version
|
||||||
|
return unless invoked_as_script? # don't want to hijack other binstubs
|
||||||
|
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
|
||||||
|
bundler_version = nil
|
||||||
|
update_index = nil
|
||||||
|
ARGV.each_with_index do |a, i|
|
||||||
|
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
||||||
|
bundler_version = a
|
||||||
|
end
|
||||||
|
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
|
||||||
|
bundler_version = $1
|
||||||
|
update_index = i
|
||||||
|
end
|
||||||
|
bundler_version
|
||||||
|
end
|
||||||
|
|
||||||
|
def gemfile
|
||||||
|
gemfile = ENV["BUNDLE_GEMFILE"]
|
||||||
|
return gemfile if gemfile && !gemfile.empty?
|
||||||
|
|
||||||
|
File.expand_path("../Gemfile", __dir__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def lockfile
|
||||||
|
lockfile =
|
||||||
|
case File.basename(gemfile)
|
||||||
|
when "gems.rb" then gemfile.sub(/\.rb$/, ".locked")
|
||||||
|
else "#{gemfile}.lock"
|
||||||
|
end
|
||||||
|
File.expand_path(lockfile)
|
||||||
|
end
|
||||||
|
|
||||||
|
def lockfile_version
|
||||||
|
return unless File.file?(lockfile)
|
||||||
|
lockfile_contents = File.read(lockfile)
|
||||||
|
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
|
||||||
|
Regexp.last_match(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def bundler_requirement
|
||||||
|
@bundler_requirement ||=
|
||||||
|
env_var_version ||
|
||||||
|
cli_arg_version ||
|
||||||
|
bundler_requirement_for(lockfile_version)
|
||||||
|
end
|
||||||
|
|
||||||
|
def bundler_requirement_for(version)
|
||||||
|
return "#{Gem::Requirement.default}.a" unless version
|
||||||
|
|
||||||
|
bundler_gem_version = Gem::Version.new(version)
|
||||||
|
|
||||||
|
bundler_gem_version.approximate_recommendation
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_bundler!
|
||||||
|
ENV["BUNDLE_GEMFILE"] ||= gemfile
|
||||||
|
|
||||||
|
activate_bundler
|
||||||
|
end
|
||||||
|
|
||||||
|
def activate_bundler
|
||||||
|
gem_error = activation_error_handling do
|
||||||
|
gem "bundler", bundler_requirement
|
||||||
|
end
|
||||||
|
return if gem_error.nil?
|
||||||
|
require_error = activation_error_handling do
|
||||||
|
require "bundler/version"
|
||||||
|
end
|
||||||
|
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
|
||||||
|
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
|
||||||
|
exit 42
|
||||||
|
end
|
||||||
|
|
||||||
|
def activation_error_handling
|
||||||
|
yield
|
||||||
|
nil
|
||||||
|
rescue StandardError, LoadError => e
|
||||||
|
e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
m.load_bundler!
|
||||||
|
|
||||||
|
if m.invoked_as_script?
|
||||||
|
load Gem.bin_path("bundler", "bundle")
|
||||||
|
end
|
||||||
|
|||||||
27
bin/rspec
Executable file
27
bin/rspec
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
#
|
||||||
|
# This file was generated by Bundler.
|
||||||
|
#
|
||||||
|
# The application 'rspec' is installed as part of a gem, and
|
||||||
|
# this file is here to facilitate running it.
|
||||||
|
#
|
||||||
|
|
||||||
|
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
||||||
|
|
||||||
|
bundle_binstub = File.expand_path("bundle", __dir__)
|
||||||
|
|
||||||
|
if File.file?(bundle_binstub)
|
||||||
|
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
|
||||||
|
load(bundle_binstub)
|
||||||
|
else
|
||||||
|
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
||||||
|
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require "rubygems"
|
||||||
|
require "bundler/setup"
|
||||||
|
|
||||||
|
load Gem.bin_path("rspec-core", "rspec")
|
||||||
@@ -27,14 +27,14 @@ module Postal
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Include from lib
|
# Include from lib
|
||||||
config.eager_load_namespaces << Postal
|
config.eager_load_paths << Rails.root.join("lib")
|
||||||
|
|
||||||
# Disable field_with_errors
|
# Disable field_with_errors
|
||||||
config.action_view.field_error_proc = proc { |t, i| t }
|
config.action_view.field_error_proc = proc { |t, i| t }
|
||||||
|
|
||||||
# Load the tracking server middleware
|
# Load the tracking server middleware
|
||||||
require "postal/tracking_middleware"
|
require "postal/tracking_middleware"
|
||||||
config.middleware.use Postal::TrackingMiddleware
|
config.middleware.insert_before ActionDispatch::HostAuthorization, Postal::TrackingMiddleware
|
||||||
|
|
||||||
config.logger = Postal.logger_for(:rails)
|
config.logger = Postal.logger_for(:rails)
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,17 @@
|
|||||||
|
|
||||||
# These inflection rules are supported but not enabled by default:
|
# These inflection rules are supported but not enabled by default:
|
||||||
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||||
inflect.acronym "API"
|
|
||||||
inflect.acronym "SMTP"
|
|
||||||
inflect.acronym "IP"
|
|
||||||
inflect.acronym "DNS"
|
|
||||||
inflect.acronym "UUID"
|
|
||||||
inflect.acronym "HTTP"
|
|
||||||
inflect.acronym "DB"
|
|
||||||
inflect.acronym "MX"
|
|
||||||
inflect.acronym "DKIM"
|
inflect.acronym "DKIM"
|
||||||
|
inflect.acronym "HTTP"
|
||||||
|
inflect.acronym "SMTP"
|
||||||
|
inflect.acronym "UUID"
|
||||||
|
|
||||||
|
inflect.acronym "API"
|
||||||
|
inflect.acronym "DNS"
|
||||||
|
|
||||||
|
inflect.acronym "DB"
|
||||||
|
inflect.acronym "IP"
|
||||||
|
inflect.acronym "MQ"
|
||||||
|
inflect.acronym "MX"
|
||||||
end
|
end
|
||||||
|
|||||||
5
config/initializers/zeitwerk.rb
Normal file
5
config/initializers/zeitwerk.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Rails.autoloaders.each do |autoloader|
|
||||||
|
# Ignore the message DB migrations directory as it doesn't follow
|
||||||
|
# Zeitwerk's conventions and is always loaded and executed in order.
|
||||||
|
autoloader.ignore(Rails.root.join('lib/postal/message_db/migrations'))
|
||||||
|
end
|
||||||
@@ -5,126 +5,122 @@
|
|||||||
# You can refer to this for a complete listing all available configuration options.
|
# You can refer to this for a complete listing all available configuration options.
|
||||||
|
|
||||||
web:
|
web:
|
||||||
host: postal.example.com
|
host: <%= ENV.fetch('POSTAL_HOST', 'postal.example.com') %>
|
||||||
protocol: https
|
protocol: <%= ENV.fetch('POSTAL_PROTOCOL', 'https') %>
|
||||||
|
|
||||||
general:
|
general:
|
||||||
use_ip_pools: false
|
use_ip_pools: <%= ENV.fetch('POSTAL_USE_IP_POOLS', 'false') %>
|
||||||
exception_url:
|
exception_url: <%= ENV.fetch('POSTAL_EXCEPTION_URL', '') %>
|
||||||
maximum_delivery_attempts: 18
|
maximum_delivery_attempts: <%= ENV.fetch('POSTAL_MAXIMUM_DELIVERY_ATTEMPTS', '18') %>
|
||||||
maximum_hold_expiry_days: 7
|
maximum_hold_expiry_days: <%= ENV.fetch('POSTAL_MAXIMUM_HOLD_EXPIRY_DAYS', '7') %>
|
||||||
suppression_list_removal_delay: 30
|
suppression_list_removal_delay: <%= ENV.fetch('POSTAL_SUPPRESSION_LIST_REMOVAL_DELAY', '30') %>
|
||||||
use_local_ns_for_domains: false
|
use_local_ns_for_domains: <%= ENV.fetch('POSTAL_USE_LOCAL_NS_FOR_DOMAINS', 'false') %>
|
||||||
default_spam_threshold: 5.0
|
default_spam_threshold: <%= ENV.fetch('POSTAL_DEFAULT_SPAM_THRESHOLD', '5.0') %>
|
||||||
default_spam_failure_threshold: 20.0
|
default_spam_failure_threshold: <%= ENV.fetch('POSTAL_DEFAULT_SPAM_FAILURE_THRESHOLD', '20.0') %>
|
||||||
use_resent_sender_header: true
|
use_resent_sender_header: <%= ENV.fetch('POSTAL_USE_RESENT_SENDER_HEADER', 'true') %>
|
||||||
|
|
||||||
web_server:
|
web_server:
|
||||||
bind_address: 127.0.0.1
|
bind_address: <%= ENV.fetch('WEB_SERVER_BIND_ADDRESS', '0.0.0.0') %>
|
||||||
port: 5000
|
port: <%= ENV.fetch('WEB_SERVER_PORT', '5000') %>
|
||||||
max_threads: 5
|
max_threads: <%= ENV.fetch('WEB_SERVER_MAX_THREADS', '5') %>
|
||||||
|
|
||||||
main_db:
|
main_db:
|
||||||
host: 127.0.0.1
|
host: <%= ENV.fetch('MAIN_DB_HOST', '127.0.0.1') %>
|
||||||
port: 3306
|
port: <%= ENV.fetch('MAIN_DB_PORT', '3306') %>
|
||||||
username: postal
|
username: <%= ENV.fetch('MAIN_DB_USERNAME', 'postal') %>
|
||||||
password:
|
password: <%= ENV.fetch('MAIN_DB_PASSWORD', '') %>
|
||||||
database: postal
|
database: <%= ENV.fetch('MAIN_DB_DATABASE', 'postal') %>
|
||||||
pool_size: 5
|
pool_size: <%= ENV.fetch('MAIN_DB_POOL_SIZE', '5') %>
|
||||||
|
|
||||||
logging:
|
|
||||||
stdout: false
|
|
||||||
root: # Automatically determined based on config root
|
|
||||||
max_log_file_size: 20
|
|
||||||
max_log_files: 10
|
|
||||||
graylog:
|
|
||||||
host:
|
|
||||||
port: 12201
|
|
||||||
|
|
||||||
message_db:
|
message_db:
|
||||||
host: 127.0.0.1
|
host: <%= ENV.fetch('MESSAGE_DB_HOST', '127.0.0.1') %>
|
||||||
port: 3306
|
port: <%= ENV.fetch('MESSAGE_DB_PORT', '3306') %>
|
||||||
username: postal
|
username: <%= ENV.fetch('MESSAGE_DB_USERNAME', 'postal') %>
|
||||||
password:
|
password: <%= ENV.fetch('MESSAGE_DB_PASSWORD', '') %>
|
||||||
prefix: postal
|
prefix: <%= ENV.fetch('MESSAGE_DB_PREFIX', 'postal') %>
|
||||||
|
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
host: 127.0.0.1
|
host: <%= ENV.fetch('RABBITMQ_HOST', '127.0.0.1') %>
|
||||||
port: 5672
|
port: <%= ENV.fetch('RABBITMQ_PORT', '5672') %>
|
||||||
tls: false
|
username: <%= ENV.fetch('RABBITMQ_USERNAME', 'postal') %>
|
||||||
verify_peer: true
|
password: <%= ENV.fetch('RABBITMQ_PASSWORD', '') %>
|
||||||
tls_ca_certificates:
|
vhost: <%= ENV.fetch('RABBITMQ_VHOST', '/postal') %>
|
||||||
- /etc/ssl/certs/ca-certificates.crt
|
tls: <%= ENV.fetch('RABBITMQ_TLS', 'false') %>
|
||||||
username: postal
|
verify_peer: <%= ENV.fetch('RABBITMQ_VERIFY_PEER', 'true') %>
|
||||||
password:
|
tls_ca_certificates: <%= ENV.fetch('RABBITMQ_TLS_CA_CERTIFICATES', '/etc/ssl/certs/ca-certificates.crt'.split(',').inspect) %>
|
||||||
vhost: /postal
|
|
||||||
|
logging:
|
||||||
|
stdout: <%= ENV.fetch('LOGGING_STDOUT', 'false') %>
|
||||||
|
root: <%= ENV.fetch('LOGGING_ROOT', '') %>
|
||||||
|
max_log_file_size: <%= ENV.fetch('LOGGING_MAX_LOG_FILES', '20') %>
|
||||||
|
max_log_files: <%= ENV.fetch('LOGGING_MAX_LOG_FILES', '10') %>
|
||||||
|
graylog:
|
||||||
|
host: <%= ENV.fetch('GRAYLOG_HOST', '') %>
|
||||||
|
port: <%= ENV.fetch('GRAYLOG_PORT', '12201') %>
|
||||||
|
|
||||||
workers:
|
workers:
|
||||||
quantity: 1
|
threads: <%= ENV.fetch('WORKER_THREADS', '4') %>
|
||||||
threads: 4
|
|
||||||
|
|
||||||
smtp_server:
|
smtp_server:
|
||||||
port: 25
|
port: <%= ENV.fetch('SMTP_SERVER_PORT', '25') %>
|
||||||
bind_address: '::'
|
bind_address: "<%= ENV.fetch('SMTP_SERVER_BIND_ADDRESS', '::') %>"
|
||||||
tls_enabled: false
|
tls_enabled: <%= ENV.fetch('SMTP_SERVER_TLS_ENABLED', 'false') %>
|
||||||
tls_certificate_path: # Defaults to config/smtp.cert
|
tls_certificate_path: <%= ENV.fetch('SMTP_SERVER_TLS_CERTIFICATE_PATH', '') %> # Defaults to config/smtp.cert
|
||||||
tls_private_key_path: # Defaults to config/smtp.key
|
tls_private_key_path: <%= ENV.fetch('SMTP_SERVER_TLS_PRIVATE_KEY_PATH', '') %> # Defaults to config/smtp.key
|
||||||
tls_ciphers:
|
tls_ciphers: <%= ENV.fetch('SMTP_SERVER_TLS_CIPHERS', '') %>
|
||||||
ssl_version: SSLv23
|
ssl_version: <%= ENV.fetch('SMTP_SERVER_SSL_VERSION', 'SSLv23') %>
|
||||||
proxy_protocol: false
|
proxy_protocol: <%= ENV.fetch('SMTP_SERVER_PROXY_PROTOCOL', 'false') %>
|
||||||
log_connect: true
|
log_connect: <%= ENV.fetch('SMTP_SERVER_LOG_CONNECT', 'false') %>
|
||||||
strip_received_headers: false
|
strip_received_headers: <%= ENV.fetch('SMTP_SERVER_STRIP_RECEIVED_HEADERS', 'false') %>
|
||||||
max_message_size: 14 # size in Megabytes
|
max_message_size: <%= ENV.fetch('SMTP_SERVER_MAX_MESSAGE_SIZE', '14') %> # size in Megabytes
|
||||||
|
|
||||||
smtp_relays:
|
smtp_relays:
|
||||||
-
|
- hostname:
|
||||||
hostname:
|
|
||||||
port: 25
|
port: 25
|
||||||
ssl_mode: Auto
|
ssl_mode: Auto
|
||||||
|
|
||||||
dns:
|
dns:
|
||||||
mx_records:
|
mx_records: <%= ENV.fetch('DNS_MX_RECORDS', 'mx.postal.example.com'.split(',').inspect) %>
|
||||||
- mx.postal.example.com
|
smtp_server_hostname: <%= ENV.fetch('DNS_SMTP_SERVER_HOSTNAME', 'postal.example.com') %>
|
||||||
smtp_server_hostname: postal.example.com
|
spf_include: <%= ENV.fetch('DNS_SPF_INCLUDE', 'spf.postal.example.com') %>
|
||||||
spf_include: spf.postal.example.com
|
return_path: <%= ENV.fetch('DNS_RETURN_PATH', 'rp.postal.example.com') %>
|
||||||
return_path: rp.postal.example.com
|
route_domain: <%= ENV.fetch('DNS_ROUTE_DOMAIN', 'routes.postal.example.com') %>
|
||||||
route_domain: routes.postal.example.com
|
track_domain: <%= ENV.fetch('DNS_TRACK_DOMAIN', 'track.postal.example.com') %>
|
||||||
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`
|
||||||
helo_hostname: # By default, this will be the same as the `smtp_server_hostname`
|
dkim_identifier: <%= ENV.fetch('DNS_DKIM_IDENTIFIER', 'postal') %>
|
||||||
dkim_identifier: postal
|
domain_verify_prefix: <%= ENV.fetch('DNS_DOMAIN_VERIFY_PREFIX', 'postal-verification') %>
|
||||||
domain_verify_prefix: postal-verification
|
custom_return_path_prefix: <%= ENV.fetch('DNS_CUSTOM_RETURN_PATH_PREFIX', 'psrp') %>
|
||||||
custom_return_path_prefix: psrp
|
|
||||||
|
|
||||||
smtp:
|
smtp:
|
||||||
host: 127.0.0.1
|
host: <%= ENV.fetch('SMTP_HOST', '127.0.0.1') %>
|
||||||
port: 25
|
port: <%= ENV.fetch('SMTP_PORT', '25') %>
|
||||||
username: # Complete when Postal is running and you can
|
username: <%= ENV.fetch('SMTP_USERNAME', '') %> # Complete when Postal is running and you can
|
||||||
password: # generate the credentials within the interface.
|
password: <%= ENV.fetch('SMTP_PASSWORD', '') %> # generate the credentials within the interface.
|
||||||
from_name: Postal
|
from_name: <%= ENV.fetch('SMTP_FROM_NAME', 'Postal') %>
|
||||||
from_address: postal@yourdomain.com
|
from_address: <%= ENV.fetch('SMTP_FROM_ADDRESS', 'postal@example.com') %>
|
||||||
|
|
||||||
rails:
|
rails:
|
||||||
environment: production
|
environment: <%= ENV.fetch('RAILS_ENV', 'production') %>
|
||||||
secret_key:
|
secret_key: <%= ENV.fetch('RAILS_SECRET_KEY_BASE', '') %>
|
||||||
|
|
||||||
rspamd:
|
rspamd:
|
||||||
enabled: false
|
enabled: <%= ENV.fetch('RSPAMD_ENABLED', 'false') %>
|
||||||
host: 127.0.0.1
|
host: <%= ENV.fetch('RSPAMD_HOST', '127.0.0.1') %>
|
||||||
port: 11334
|
port: <%= ENV.fetch('RSPAMD_PORT', '11334') %>
|
||||||
ssl: false
|
ssl: <%= ENV.fetch('RSPAMD_SSL', 'false') %>
|
||||||
password: null
|
password: <%= ENV.fetch('RSPAMD_PASSWORD', '') %>
|
||||||
flags: null
|
flags: <%= ENV.fetch('RSPAMD_FLAGS', '') %>
|
||||||
|
|
||||||
spamd:
|
spamd:
|
||||||
enabled: false
|
enabled: <%= ENV.fetch('SPAMD_ENABLED', 'false') %>
|
||||||
host: 127.0.0.1
|
host: <%= ENV.fetch('SPAMD_HOST', '127.0.0.1') %>
|
||||||
port: 783
|
port: <%= ENV.fetch('SPAMD_PORT', '783') %>
|
||||||
|
|
||||||
clamav:
|
clamav:
|
||||||
enabled: false
|
enabled: <%= ENV.fetch('CLAMAV_ENABLED', 'false') %>
|
||||||
host: 127.0.0.1
|
host: <%= ENV.fetch('CLAMAV_HOST', '127.0.0.1') %>
|
||||||
port: 2000
|
port: <%= ENV.fetch('CLAMAV_PORT', '2000') %>
|
||||||
|
|
||||||
smtp_client:
|
smtp_client:
|
||||||
open_timeout: 30
|
open_timeout: <%= ENV.fetch('SMTP_CLIENT_OPEN_TIMEOUT', '30') %>
|
||||||
read_timeout: 60
|
read_timeout: <%= ENV.fetch('SMTP_CLIENT_READ_TIMEOUT', '60') %>
|
||||||
|
|||||||
@@ -1,45 +1,2 @@
|
|||||||
module Postal
|
module Postal
|
||||||
|
|
||||||
extend ActiveSupport::Autoload
|
|
||||||
|
|
||||||
eager_autoload do
|
|
||||||
autoload :AppLogger
|
|
||||||
autoload :BounceMessage
|
|
||||||
autoload :Config
|
|
||||||
autoload :Countries
|
|
||||||
autoload :DKIMHeader
|
|
||||||
autoload :Error
|
|
||||||
autoload :Helpers
|
|
||||||
autoload :HTTP
|
|
||||||
autoload :HTTPSender
|
|
||||||
autoload :Job
|
|
||||||
autoload :MessageDB
|
|
||||||
autoload :MessageInspection
|
|
||||||
autoload :MessageInspector
|
|
||||||
autoload :MessageInspectors
|
|
||||||
autoload :MessageParser
|
|
||||||
autoload :MessageRequeuer
|
|
||||||
autoload :MXLookup
|
|
||||||
autoload :QueryString
|
|
||||||
autoload :RabbitMQ
|
|
||||||
autoload :ReplySeparator
|
|
||||||
autoload :RspecHelpers
|
|
||||||
autoload :Sender
|
|
||||||
autoload :SendResult
|
|
||||||
autoload :SMTPSender
|
|
||||||
autoload :SMTPServer
|
|
||||||
autoload :SpamCheck
|
|
||||||
autoload :TrackingMiddleware
|
|
||||||
autoload :UserCreator
|
|
||||||
autoload :Version
|
|
||||||
autoload :Worker
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.eager_load!
|
|
||||||
super
|
|
||||||
Postal::MessageDB.eager_load!
|
|
||||||
Postal::SMTPServer.eager_load!
|
|
||||||
Postal::MessageInspectors.eager_load!
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
require "erb"
|
||||||
require "yaml"
|
require "yaml"
|
||||||
require "pathname"
|
require "pathname"
|
||||||
require "cgi"
|
require "cgi"
|
||||||
@@ -78,7 +79,11 @@ module Postal
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.defaults
|
def self.defaults
|
||||||
@defaults ||= YAML.load_file(defaults_file_path)
|
@defaults ||= begin
|
||||||
|
file = File.read(defaults_file_path)
|
||||||
|
yaml = ERB.new(file).result
|
||||||
|
YAML.safe_load(yaml)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.database_url
|
def self.database_url
|
||||||
@@ -152,7 +157,7 @@ module Postal
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.signing_key_path
|
def self.signing_key_path
|
||||||
config_root.join("signing.key")
|
ENV.fetch("POSTAL_SIGNING_KEY_PATH") { config_root.join("signing.key") }
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.signing_key
|
def self.signing_key
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module Postal
|
|||||||
|
|
||||||
def initialize(id, params = {})
|
def initialize(id, params = {})
|
||||||
@id = id
|
@id = id
|
||||||
@params = params.with_indifferent_access
|
@params = params
|
||||||
on_initialize
|
on_initialize
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -38,9 +38,5 @@ module Postal
|
|||||||
job_id
|
job_id
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.perform(params = {})
|
|
||||||
new(nil, params).perform
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,20 +1,4 @@
|
|||||||
module Postal
|
module Postal
|
||||||
module MessageDB
|
module MessageDB
|
||||||
|
|
||||||
extend ActiveSupport::Autoload
|
|
||||||
eager_autoload do
|
|
||||||
autoload :Click
|
|
||||||
autoload :Database
|
|
||||||
autoload :Delivery
|
|
||||||
autoload :LiveStats
|
|
||||||
autoload :Load
|
|
||||||
autoload :Message
|
|
||||||
autoload :Migration
|
|
||||||
autoload :Provisioner
|
|
||||||
autoload :Statistics
|
|
||||||
autoload :SuppressionList
|
|
||||||
autoload :Webhooks
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ module Postal
|
|||||||
#
|
#
|
||||||
# Insert a new raw message into a table (creating it if needed)
|
# Insert a new raw message into a table (creating it if needed)
|
||||||
#
|
#
|
||||||
def insert_raw_message(data, date = Date.today)
|
def insert_raw_message(data, date = Time.now.utc.to_date)
|
||||||
table_name = raw_table_name_for_date(date)
|
table_name = raw_table_name_for_date(date)
|
||||||
begin
|
begin
|
||||||
headers, body = data.split(/\r?\n\r?\n/, 2)
|
headers, body = data.split(/\r?\n\r?\n/, 2)
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ module Postal
|
|||||||
return unless @pending_raw_message
|
return unless @pending_raw_message
|
||||||
|
|
||||||
self.size = @pending_raw_message.bytesize
|
self.size = @pending_raw_message.bytesize
|
||||||
date = Date.today
|
date = Time.now.utc.to_date
|
||||||
table_name, headers_id, body_id = @database.insert_raw_message(@pending_raw_message, date)
|
table_name, headers_id, body_id = @database.insert_raw_message(@pending_raw_message, date)
|
||||||
self.raw_table = table_name
|
self.raw_table = table_name
|
||||||
self.raw_headers_id = headers_id
|
self.raw_headers_id = headers_id
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ module Postal
|
|||||||
# Return a list of raw message tables that are older than the given date
|
# Return a list of raw message tables that are older than the given date
|
||||||
#
|
#
|
||||||
def raw_tables(max_age = 30)
|
def raw_tables(max_age = 30)
|
||||||
earliest_date = max_age ? Date.today - max_age : nil
|
earliest_date = max_age ? Time.now.utc.to_date - max_age : nil
|
||||||
[].tap do |tables|
|
[].tap do |tables|
|
||||||
@database.query("SHOW TABLES FROM `#{@database.database_name}` LIKE 'raw-%'").each do |tbl|
|
@database.query("SHOW TABLES FROM `#{@database.database_name}` LIKE 'raw-%'").each do |tbl|
|
||||||
tbl_name = tbl.to_a.first.last
|
tbl_name = tbl.to_a.first.last
|
||||||
@@ -128,7 +128,7 @@ module Postal
|
|||||||
# Remove messages from the messages table that are too old to retain
|
# Remove messages from the messages table that are too old to retain
|
||||||
#
|
#
|
||||||
def remove_messages(max_age = 60)
|
def remove_messages(max_age = 60)
|
||||||
time = (Date.today - max_age.days).to_time.end_of_day
|
time = (Time.now.utc.to_date - max_age.days).to_time.end_of_day
|
||||||
return unless newest_message_to_remove = @database.select(:messages, where: { timestamp: { less_than_or_equal_to: time.to_f } }, limit: 1, order: :id, direction: "DESC", fields: [:id]).first
|
return unless newest_message_to_remove = @database.select(:messages, where: { timestamp: { less_than_or_equal_to: time.to_f } }, limit: 1, order: :id, direction: "DESC", fields: [:id]).first
|
||||||
|
|
||||||
id = newest_message_to_remove["id"]
|
id = newest_message_to_remove["id"]
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
module Postal
|
module Postal
|
||||||
module MessageInspectors
|
module MessageInspectors
|
||||||
|
|
||||||
extend ActiveSupport::Autoload
|
|
||||||
eager_autoload do
|
|
||||||
autoload :Clamav
|
|
||||||
autoload :Rspamd
|
|
||||||
autoload :SpamAssassin
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
module Postal
|
module Postal
|
||||||
module SMTPServer
|
module SMTPServer
|
||||||
|
|
||||||
extend ActiveSupport::Autoload
|
|
||||||
eager_autoload do
|
|
||||||
autoload :Client
|
|
||||||
autoload :Server
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
50
script/send-html-email.rb
Normal file
50
script/send-html-email.rb
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# This script will automatically send an HTML email to the
|
||||||
|
# SMTP server given.
|
||||||
|
|
||||||
|
require 'mail'
|
||||||
|
require 'net/smtp'
|
||||||
|
|
||||||
|
from = ARGV[0]
|
||||||
|
to = ARGV[1]
|
||||||
|
|
||||||
|
if from.nil? || to.nil?
|
||||||
|
puts "Usage: ruby send-html-email.rb <from> <to>"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
mail = Mail.new
|
||||||
|
mail.to = to
|
||||||
|
mail.from = from
|
||||||
|
mail.subject = "A test email from #{Time.now.to_s}"
|
||||||
|
mail['X-Postal-Tag'] = 'send-html-email-script'
|
||||||
|
mail.text_part = Mail::Part.new do
|
||||||
|
body <<~BODY
|
||||||
|
Hello there.
|
||||||
|
|
||||||
|
This is an example. It doesn't do all that much.
|
||||||
|
|
||||||
|
Some other characters: őúéáűí
|
||||||
|
|
||||||
|
There is a link here through... https://postalserver.io/test-plain-text-link?foo=bar&baz=qux
|
||||||
|
BODY
|
||||||
|
end
|
||||||
|
mail.html_part = Mail::Part.new do
|
||||||
|
content_type 'text/html; charset=UTF-8'
|
||||||
|
body <<~BODY
|
||||||
|
<p>Hello there</p>
|
||||||
|
<p>This is an example email. It doesn't do all that much.</p>
|
||||||
|
<p>Some other characters: őúéáűí</p>
|
||||||
|
<p>There is a <a href='https://postalserver.io/test-plain-text-link?foo=bar&baz=qux'>link here</a> though...</p>
|
||||||
|
BODY
|
||||||
|
end
|
||||||
|
|
||||||
|
#puts mail.to_s
|
||||||
|
|
||||||
|
Net::SMTP.start('127.0.0.1', 2525) do |smtp|
|
||||||
|
smtp.send_message mail.to_s, mail.from.first, mail.to.first
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "Sent"
|
||||||
المرجع في مشكلة جديدة
حظر مستخدم