diff --git a/net/postfix/Makefile b/net/postfix/Makefile
new file mode 100644
index 000000000..d86da052c
--- /dev/null
+++ b/net/postfix/Makefile
@@ -0,0 +1,8 @@
+PLUGIN_NAME= postfix
+PLUGIN_VERSION= 0.1
+PLUGIN_COMMENT= SMTP mail relay
+PLUGIN_DEPENDS= postfix-sasl
+PLUGIN_MAINTAINER= m.muenz@gmail.com
+PLUGIN_DEVEL= YES
+
+.include "../../Mk/plugins.mk"
diff --git a/net/postfix/pkg-descr b/net/postfix/pkg-descr
new file mode 100644
index 000000000..1e96d0531
--- /dev/null
+++ b/net/postfix/pkg-descr
@@ -0,0 +1,5 @@
+Postfix attempts to be fast, easy to administer, and secure.
+The outside has a definite Sendmail-ish flavor, but the inside
+is completely different.
+
+WWW: http://www.postfix.org/
diff --git a/net/postfix/src/etc/inc/plugins.inc.d/postfix.inc b/net/postfix/src/etc/inc/plugins.inc.d/postfix.inc
new file mode 100644
index 000000000..0ff66f9de
--- /dev/null
+++ b/net/postfix/src/etc/inc/plugins.inc.d/postfix.inc
@@ -0,0 +1,49 @@
+ gettext('Postfix'),
+ 'configd' => array(
+ 'restart' => array('postfix restart'),
+ 'start' => array('postfix start'),
+ 'stop' => array('postfix stop'),
+ ),
+ 'name' => 'postfix',
+ 'pidfile' => '/var/spool/postfix/pid/master.pid'
+ );
+ }
+
+ return $services;
+}
diff --git a/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/AntispamController.php b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/AntispamController.php
new file mode 100644
index 000000000..c9896d712
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/AntispamController.php
@@ -0,0 +1,76 @@
+request->isGet()) {
+ $mdlAntispam = new Antispam();
+ $result['antispam'] = $mdlAntispam->getNodes();
+ }
+ return $result;
+ }
+
+ public function setAction()
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost()) {
+ // load model and update with provided data
+ $mdlAntispam = new Antispam();
+ $mdlAntispam->setNodes($this->request->getPost("antispam"));
+
+ // perform validation
+ $valMsgs = $mdlAntispam->performValidation();
+ foreach ($valMsgs as $field => $msg) {
+ if (!array_key_exists("validations", $result)) {
+ $result["validations"] = array();
+ }
+ $result["validations"]["antispam.".$msg->getField()] = $msg->getMessage();
+ }
+
+ // serialize model to config and save
+ if ($valMsgs->count() == 0) {
+ $mdlAntispam->serializeToConfig();
+ Config::getInstance()->save();
+ $result["result"] = "saved";
+ }
+ }
+ return $result;
+ }
+}
diff --git a/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/DomainController.php b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/DomainController.php
new file mode 100644
index 000000000..f449b9245
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/DomainController.php
@@ -0,0 +1,205 @@
+request->isGet()) {
+ $mdlDomain = new Domain();
+ $result['domain'] = $mdlDomain->getNodes();
+ }
+ return $result;
+ }
+
+ public function setAction()
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost()) {
+ // load model and update with provided data
+ $mdlDomain = new Domain();
+ $mdlDomain->setNodes($this->request->getPost("domain"));
+ // perform validation
+ $valMsgs = $mdlDomain->performValidation();
+ foreach ($valMsgs as $field => $msg) {
+ if (!array_key_exists("validations", $result)) {
+ $result["validations"] = array();
+ }
+ $result["validations"]["domain.".$msg->getField()] = $msg->getMessage();
+ }
+ // serialize model to config and save
+ if ($valMsgs->count() == 0) {
+ $mdlDomain->serializeToConfig();
+ Config::getInstance()->save();
+ $result["result"] = "saved";
+ }
+ }
+ return $result;
+ }
+
+ public function searchDomainAction()
+ {
+ $this->sessionClose();
+ $mdlDomain = $this->getModel();
+ $grid = new UIModelGrid($mdlDomain->domains->domain);
+ return $grid->fetchBindRequest(
+ $this->request,
+ array("enabled", "domainname", "destination" )
+ );
+ }
+
+ public function getDomainAction($uuid = null)
+ {
+ $mdlDomain = $this->getModel();
+ if ($uuid != null) {
+ $node = $mdlDomain->getNodeByReference('domains.domain.' . $uuid);
+ if ($node != null) {
+ // return node
+ return array("domain" => $node->getNodes());
+ }
+ } else {
+ $node = $mdlDomain->domains->domain->add();
+ return array("domain" => $node->getNodes());
+ }
+ return array();
+ }
+
+ public function addDomainAction()
+ {
+ $result = array("result" => "failed");
+ if ($this->request->isPost() && $this->request->hasPost("domain")) {
+ $result = array("result" => "failed", "validations" => array());
+ $mdlDomain = $this->getModel();
+ $node = $mdlDomain->domains->domain->Add();
+ $node->setNodes($this->request->getPost("domain"));
+ $valMsgs = $mdlDomain->performValidation();
+ foreach ($valMsgs as $field => $msg) {
+ $fieldnm = str_replace($node->__reference, "domain", $msg->getField());
+ $result["validations"][$fieldnm] = $msg->getMessage();
+ }
+ if (count($result['validations']) == 0) {
+ unset($result['validations']);
+ // save config if validated correctly
+ $mdlDomain->serializeToConfig();
+ Config::getInstance()->save();
+ unset($result['validations']);
+ $result["result"] = "saved";
+ }
+ }
+ return $result;
+ }
+
+ public function delDomainAction($uuid)
+ {
+ $result = array("result" => "failed");
+ if ($this->request->isPost()) {
+ $mdlDomain = $this->getModel();
+ if ($uuid != null) {
+ if ($mdlDomain->domains->domain->del($uuid)) {
+ $mdlDomain->serializeToConfig();
+ Config::getInstance()->save();
+ $result['result'] = 'deleted';
+ } else {
+ $result['result'] = 'not found';
+ }
+ }
+ }
+ return $result;
+ }
+
+ public function setDomainAction($uuid)
+ {
+ if ($this->request->isPost() && $this->request->hasPost("domain")) {
+ $mdlSetting = $this->getModel();
+ if ($uuid != null) {
+ $node = $mdlSetting->getNodeByReference('domains.domain.' . $uuid);
+ if ($node != null) {
+ $result = array("result" => "failed", "validations" => array());
+ $domainInfo = $this->request->getPost("domain");
+ $node->setNodes($domainInfo);
+ $valMsgs = $mdlSetting->performValidation();
+ foreach ($valMsgs as $field => $msg) {
+ $fieldnm = str_replace($node->__reference, "domain", $msg->getField());
+ $result["validations"][$fieldnm] = $msg->getMessage();
+ }
+ if (count($result['validations']) == 0) {
+ // save config if validated correctly
+ $mdlSetting->serializeToConfig();
+ Config::getInstance()->save();
+ $result = array("result" => "saved");
+ }
+ return $result;
+ }
+ }
+ }
+ return array("result" => "failed");
+ }
+
+ public function toggle_handler($uuid, $elements, $element)
+ {
+ $result = array("result" => "failed");
+ if ($this->request->isPost()) {
+ $mdlSetting = $this->getModel();
+ if ($uuid != null) {
+ $node = $mdlSetting->getNodeByReference($elements . '.'. $element .'.' . $uuid);
+ if ($node != null) {
+ if ($node->enabled->__toString() == "1") {
+ $result['result'] = "Disabled";
+ $node->enabled = "0";
+ } else {
+ $result['result'] = "Enabled";
+ $node->enabled = "1";
+ }
+ // if item has toggled, serialize to config and save
+ $mdlSetting->serializeToConfig();
+ Config::getInstance()->save();
+ }
+ }
+ }
+ return $result;
+ }
+
+ public function toggleDomainAction($uuid)
+ {
+ return $this->toggle_handler($uuid, 'domains', 'domain');
+ }
+}
diff --git a/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/GeneralController.php b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/GeneralController.php
new file mode 100644
index 000000000..b01f39fea
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/GeneralController.php
@@ -0,0 +1,76 @@
+request->isGet()) {
+ $mdlGeneral = new General();
+ $result['general'] = $mdlGeneral->getNodes();
+ }
+ return $result;
+ }
+
+ public function setAction()
+ {
+ $result = array("result"=>"failed");
+ if ($this->request->isPost()) {
+ // load model and update with provided data
+ $mdlGeneral = new General();
+ $mdlGeneral->setNodes($this->request->getPost("general"));
+
+ // perform validation
+ $valMsgs = $mdlGeneral->performValidation();
+ foreach ($valMsgs as $field => $msg) {
+ if (!array_key_exists("validations", $result)) {
+ $result["validations"] = array();
+ }
+ $result["validations"]["general.".$msg->getField()] = $msg->getMessage();
+ }
+
+ // serialize model to config and save
+ if ($valMsgs->count() == 0) {
+ $mdlGeneral->serializeToConfig();
+ Config::getInstance()->save();
+ $result["result"] = "saved";
+ }
+ }
+ return $result;
+ }
+}
diff --git a/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/ServiceController.php b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/ServiceController.php
new file mode 100644
index 000000000..269363bfe
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/Api/ServiceController.php
@@ -0,0 +1,159 @@
+configdRun("firmware plugin rspamd");
+ return $response;
+ }
+
+ /**
+ * start postfix service (in background)
+ * @return array
+ */
+ public function startAction()
+ {
+ if ($this->request->isPost()) {
+ $backend = new Backend();
+ $response = $backend->configdRun('postfix start');
+ return array("response" => $response);
+ } else {
+ return array("response" => array());
+ }
+ }
+
+ /**
+ * stop postfix service
+ * @return array
+ */
+ public function stopAction()
+ {
+ if ($this->request->isPost()) {
+ $backend = new Backend();
+ $response = $backend->configdRun("postfix stop");
+ return array("response" => $response);
+ } else {
+ return array("response" => array());
+ }
+ }
+
+ /**
+ * restart postfix service
+ * @return array
+ */
+ public function restartAction()
+ {
+ if ($this->request->isPost()) {
+ $backend = new Backend();
+ $response = $backend->configdRun("postfix restart");
+ return array("response" => $response);
+ } else {
+ return array("response" => array());
+ }
+ }
+
+ /**
+ * retrieve status of postfix
+ * @return array
+ * @throws \Exception
+ */
+ public function statusAction()
+ {
+ $backend = new Backend();
+ $mdlGeneral = new General();
+ $response = $backend->configdRun("postfix status");
+
+ if (strpos($response, "not running") > 0) {
+ if ($mdlGeneral->enabled->__toString() == 1) {
+ $status = "stopped";
+ } else {
+ $status = "disabled";
+ }
+ } elseif (strpos($response, "is running") > 0) {
+ $status = "running";
+ } elseif ($mdlGeneral->enabled->__toString() == 0) {
+ $status = "disabled";
+ } else {
+ $status = "unkown";
+ }
+
+
+ return array("status" => $status);
+ }
+
+ /**
+ * reconfigure postfix, generate config and reload
+ */
+ public function reconfigureAction()
+ {
+ if ($this->request->isPost()) {
+ // close session for long running action
+ $this->sessionClose();
+
+ $mdlGeneral = new General();
+ $backend = new Backend();
+
+ $runStatus = $this->statusAction();
+
+ // stop postfix if it is running or not
+ $this->stopAction();
+
+ // generate template
+ $backend->configdRun('template reload OPNsense/Postfix');
+ $backend->configdRun('postfix make-transport');
+
+ // (res)start daemon
+ if ($mdlGeneral->enabled->__toString() == 1) {
+ $this->startAction();
+ }
+
+ return array("status" => "ok");
+ } else {
+ return array("status" => "failed");
+ }
+ }
+}
diff --git a/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/DomainController.php b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/DomainController.php
new file mode 100644
index 000000000..1f4e7b844
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/DomainController.php
@@ -0,0 +1,34 @@
+view->title = gettext("Postfix Domains");
+ $this->view->formDialogEditPostfixDomain = $this->getForm("dialogEditPostfixDomain");
+ $this->view->pick('OPNsense/Postfix/domain');
+ }
+}
diff --git a/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/GeneralController.php b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/GeneralController.php
new file mode 100644
index 000000000..3714878b5
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/GeneralController.php
@@ -0,0 +1,39 @@
+view->title = gettext("Postfix Settings");
+ $this->view->generalForm = $this->getForm("general");
+ $this->view->antispamForm = $this->getForm("antispam");
+ $this->view->pick('OPNsense/Postfix/general');
+ }
+}
diff --git a/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/antispam.xml b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/antispam.xml
new file mode 100644
index 000000000..9db8d64b3
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/antispam.xml
@@ -0,0 +1,8 @@
+
diff --git a/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/dialogEditPostfixDomain.xml b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/dialogEditPostfixDomain.xml
new file mode 100644
index 000000000..ce4c6b020
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/dialogEditPostfixDomain.xml
@@ -0,0 +1,20 @@
+
diff --git a/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/general.xml b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/general.xml
new file mode 100644
index 000000000..009ee894a
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/controllers/OPNsense/Postfix/forms/general.xml
@@ -0,0 +1,44 @@
+
diff --git a/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/ACL/ACL.xml b/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/ACL/ACL.xml
new file mode 100644
index 000000000..65c0078e5
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/ACL/ACL.xml
@@ -0,0 +1,9 @@
+
+
+ Services: Postfix
+
+ ui/postfix/*
+ api/postfix/*
+
+
+
diff --git a/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Antispam.php b/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Antispam.php
new file mode 100644
index 000000000..50598c8ca
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Antispam.php
@@ -0,0 +1,34 @@
+
+ //OPNsense/postfix/antispam
+ Postfix Antispam configuration
+ 1.0.0
+
+
+ 0
+ Y
+
+
+
diff --git a/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Domain.php b/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Domain.php
new file mode 100644
index 000000000..2bcab6c9e
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Domain.php
@@ -0,0 +1,30 @@
+
+ //OPNsense/postfix/domain
+ Postfix domain configuration
+ 1.0.0
+
+
+
+
+ 1
+ Y
+
+
+
+ Y
+
+
+
+ Y
+
+
+
+
+
diff --git a/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/General.php b/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/General.php
new file mode 100644
index 000000000..5fe1c41cd
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/General.php
@@ -0,0 +1,34 @@
+
+ //OPNsense/postfix/general
+ Postfix configuration
+ 1.0.0
+
+
+ 0
+ Y
+
+
+
+ N
+
+
+
+ N
+
+
+
+ N
+
+
+ all
+ Y
+
+
+ 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
+ Y
+
+
+
+ N
+
+
+
diff --git a/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Menu/Menu.xml b/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Menu/Menu.xml
new file mode 100644
index 000000000..6c9e95893
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/models/OPNsense/Postfix/Menu/Menu.xml
@@ -0,0 +1,8 @@
+
diff --git a/net/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/domain.volt b/net/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/domain.volt
new file mode 100644
index 000000000..ccb964315
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/domain.volt
@@ -0,0 +1,110 @@
+{#
+
+OPNsense® is Copyright © 2014 – 2017 by Deciso B.V.
+Copyright (C) 2017 Michael Muenz
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+#}
+
+
+
+
+
+
+
+
+
+ | {{ lang._('Enabled') }} |
+ {{ lang._('Domain') }} |
+ {{ lang._('Destination') }} |
+ {{ lang._('ID') }} |
+ {{ lang._('Commands') }} |
+
+
+
+
+
+ |
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+{{ partial("layout_partials/base_dialog",['fields':formDialogEditPostfixDomain,'id':'dialogEditPostfixDomain','label':lang._('Edit Domain')])}}
diff --git a/net/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/general.volt b/net/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/general.volt
new file mode 100644
index 000000000..318606e52
--- /dev/null
+++ b/net/postfix/src/opnsense/mvc/app/views/OPNsense/Postfix/general.volt
@@ -0,0 +1,100 @@
+{#
+
+OPNsense® is Copyright © 2014 – 2017 by Deciso B.V.
+This file is Copyright © 2017 by Michael Muenz
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+#}
+
+
+
+
+ {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_general_settings'])}}
+
+
+
+
+
+
+
+
+
+
{{ lang._('No Rspamd plugin found, please install and come back.')}}
+
+ {{ partial("layout_partials/base_form",['fields':antispamForm,'id':'frm_antispam_settings'])}}
+
+
+
+
+
+
+
+
+
diff --git a/net/postfix/src/opnsense/scripts/OPNsense/Postfix/setup.sh b/net/postfix/src/opnsense/scripts/OPNsense/Postfix/setup.sh
new file mode 100644
index 000000000..09a32313b
--- /dev/null
+++ b/net/postfix/src/opnsense/scripts/OPNsense/Postfix/setup.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+mkdir -p /var/spool/postfix/
+chown root:wheel /var/spool/postfix
+chmod 755 /var/spool/postfix
+
+# Set defaults
+POSTFIX_DIRS="/var/spool/postfix/active /var/spool/postfix/bounce /var/spool/postfix/corrupt /var/spool/postfix/defer /var/spool/postfix/deferred /var/spool/postfix/flush /var/spool/postfix/hold /var/spool/postfix/incoming /var/spool/postfix/private /var/spool/postfix/saved /var/spool/postfix/trace /var/db/postfix"
+POSTFIX_USER=postfix
+POSTFIX_GROUP=wheel
+
+for DIR in ${POSTFIX_DIRS}; do
+ mkdir -p ${DIR}
+ chmod -R 700 ${DIR}
+ chown -R ${POSTFIX_USER}:${POSTFIX_GROUP} ${DIR}
+done
+
+# Some folders need special attention
+mkdir -p /var/spool/postfix/maildrop
+mkdir -p /var/spool/postfix/public
+mkdir -p /var/spool/postfix/pid
+chmod -R 730 /var/spool/postfix/maildrop
+chmod -R 710 /var/spool/postfix/public
+chmod -R 755 /var/spool/postfix/pid
+chown -R postfix:maildrop /var/spool/postfix/maildrop
+chown -R postfix:maildrop /var/spool/postfix/public
+chown -R root:postfix /var/spool/postfix/pid
+
+# Create Transporttable
+postmap /usr/local/etc/postfix/transport
diff --git a/net/postfix/src/opnsense/service/conf/actions.d/actions_postfix.conf b/net/postfix/src/opnsense/service/conf/actions.d/actions_postfix.conf
new file mode 100644
index 000000000..29449e264
--- /dev/null
+++ b/net/postfix/src/opnsense/service/conf/actions.d/actions_postfix.conf
@@ -0,0 +1,35 @@
+[start]
+command:/usr/local/opnsense/scripts/OPNsense/Postfix/setup.sh;/usr/local/etc/rc.d/postfix start
+parameters:
+type:script
+message:starting Postfix
+
+[stop]
+command:/usr/local/etc/rc.d/postfix stop; exit 0
+parameters:
+type:script
+message:stopping Postfix
+
+[restart]
+command:/usr/local/opnsense/scripts/OPNsense/Postfix/setup.sh;/usr/local/etc/rc.d/postfix restart
+parameters:
+type:script
+message:restarting Postfix
+
+[reconfigure]
+command:/usr/local/opnsense/scripts/OPNsense/Postfix/setup.sh;/usr/local/etc/rc.d/postfix reload
+parameters:
+type:script
+message:reconfigure Postfix
+
+[status]
+command:/usr/local/etc/rc.d/postfix status;exit 0
+parameters:
+type:script_output
+message:request Postfix status
+
+[make-transport]
+command:postmap /usr/local/etc/postfix/transport
+parameters:
+type:script_output
+message:rebuilding Transporttable
\ No newline at end of file
diff --git a/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/+TARGETS b/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/+TARGETS
new file mode 100644
index 000000000..be5ab0c93
--- /dev/null
+++ b/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/+TARGETS
@@ -0,0 +1,4 @@
+main.cf:/usr/local/etc/postfix/main.cf
+master.cf:/usr/local/etc/postfix/master.cf
+postfix:/etc/rc.conf.d/postfix
+transport:/usr/local/etc/postfix/transport
diff --git a/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/main.cf b/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/main.cf
new file mode 100644
index 000000000..553604631
--- /dev/null
+++ b/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/main.cf
@@ -0,0 +1,75 @@
+{% if helpers.exists('OPNsense.postfix.general.enabled') and OPNsense.postfix.general.enabled == '1' %}
+
+##########################
+# START SYSTEM DEFAULTS
+##########################
+compatibility_level = 2
+queue_directory = /var/spool/postfix
+command_directory = /usr/local/sbin
+daemon_directory = /usr/local/libexec/postfix
+data_directory = /var/db/postfix
+mail_owner = postfix
+unknown_local_recipient_reject_code = 550
+mynetworks_style = host
+debug_peer_level = 2
+debugger_command =
+ PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
+ ddd $daemon_directory/$process_name $process_id & sleep 5
+
+sendmail_path = /usr/local/sbin/sendmail
+newaliases_path = /usr/local/bin/newaliases
+mailq_path = /usr/local/bin/mailq
+setgid_group = maildrop
+html_directory = no
+manpage_directory = /usr/local/man
+sample_directory = /usr/local/etc/postfix
+readme_directory = no
+inet_protocols = all
+meta_directory = /usr/local/libexec/postfix
+shlib_directory = /usr/local/lib/postfix
+relay_domains = hash:/usr/local/etc/postfix/transport
+transport_maps = hash:/usr/local/etc/postfix/transport
+##########################
+# END SYSTEM DEFAULTS
+##########################
+
+{% if helpers.exists('OPNsense.postfix.general.myhostname') and OPNsense.postfix.general.myhostname != '' %}
+myhostname = {{ OPNsense.postfix.general.myhostname }}
+{% else %}
+myhostname = {{ system.hostname }}
+{% endif %}
+{% if helpers.exists('OPNsense.postfix.general.mydomain') and OPNsense.postfix.general.mydomain != '' %}
+mydomain = {{ OPNsense.postfix.general.mydomain }}
+{% else %}
+mydomain = {{ system.domain }}
+{% endif %}
+{% if helpers.exists('OPNsense.postfix.general.myorigin') and OPNsense.postfix.general.myorigin != '' %}
+myorigin = {{ OPNsense.postfix.general.myorigin }}
+{% else %}
+myorigin = $myhostname
+{% endif %}
+{% if helpers.exists('OPNsense.postfix.general.inet_interfaces') and OPNsense.postfix.general.inet_interfaces != '' %}
+inet_interfaces = {{ OPNsense.postfix.general.inet_interfaces }}
+{% else %}
+inet_interfaces = all
+{% endif %}
+{% if helpers.exists('OPNsense.postfix.general.mynetworks') and OPNsense.postfix.general.mynetworks != '' %}
+mynetworks = {{ OPNsense.postfix.general.mynetworks }}
+{% else %}
+mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
+{% endif %}
+{% if helpers.exists('OPNsense.postfix.general.banner') and OPNsense.postfix.general.banner != '' %}
+smtpd_banner = {{ OPNsense.postfix.general.banner }}
+{% else %}
+smtpd_banner = $myhostname ESMTP Postfix
+{% endif %}
+
+{% if helpers.exists('OPNsense.postfix.antispam.enable_rspamd') and OPNsense.postfix.antispam.enable_rspamd == '1' %}
+smtpd_milters = inet:localhost:11332
+non_smtpd_milters = inet:localhost:11332
+milter_protocol = 6
+milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
+milter_default_action = accept
+{% endif %}
+
+{% endif %}
diff --git a/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/master.cf b/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/master.cf
new file mode 100644
index 000000000..4fb709e7f
--- /dev/null
+++ b/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/master.cf
@@ -0,0 +1,135 @@
+{% if helpers.exists('OPNsense.postfix.general.enabled') and OPNsense.postfix.general.enabled == '1' %}
+#
+# Postfix master process configuration file. For details on the format
+# of the file, see the master(5) manual page (command: "man 5 master" or
+# on-line: http://www.postfix.org/master.5.html).
+#
+# Do not forget to execute "postfix reload" after editing this file.
+#
+# ==========================================================================
+# service type private unpriv chroot wakeup maxproc command + args
+# (yes) (yes) (no) (never) (100)
+# ==========================================================================
+smtp inet n - n - - smtpd
+#smtp inet n - n - 1 postscreen
+#smtpd pass - - n - - smtpd
+#dnsblog unix - - n - 0 dnsblog
+#tlsproxy unix - - n - 0 tlsproxy
+#submission inet n - n - - smtpd
+# -o syslog_name=postfix/submission
+# -o smtpd_tls_security_level=encrypt
+# -o smtpd_sasl_auth_enable=yes
+# -o smtpd_tls_auth_only=yes
+# -o smtpd_reject_unlisted_recipient=no
+# -o smtpd_client_restrictions=$mua_client_restrictions
+# -o smtpd_helo_restrictions=$mua_helo_restrictions
+# -o smtpd_sender_restrictions=$mua_sender_restrictions
+# -o smtpd_recipient_restrictions=
+# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
+# -o milter_macro_daemon_name=ORIGINATING
+#smtps inet n - n - - smtpd
+# -o syslog_name=postfix/smtps
+# -o smtpd_tls_wrappermode=yes
+# -o smtpd_sasl_auth_enable=yes
+# -o smtpd_reject_unlisted_recipient=no
+# -o smtpd_client_restrictions=$mua_client_restrictions
+# -o smtpd_helo_restrictions=$mua_helo_restrictions
+# -o smtpd_sender_restrictions=$mua_sender_restrictions
+# -o smtpd_recipient_restrictions=
+# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
+# -o milter_macro_daemon_name=ORIGINATING
+#628 inet n - n - - qmqpd
+pickup unix n - n 60 1 pickup
+cleanup unix n - n - 0 cleanup
+qmgr unix n - n 300 1 qmgr
+#qmgr unix n - n 300 1 oqmgr
+tlsmgr unix - - n 1000? 1 tlsmgr
+rewrite unix - - n - - trivial-rewrite
+bounce unix - - n - 0 bounce
+defer unix - - n - 0 bounce
+trace unix - - n - 0 bounce
+verify unix - - n - 1 verify
+flush unix n - n 1000? 0 flush
+proxymap unix - - n - - proxymap
+proxywrite unix - - n - 1 proxymap
+smtp unix - - n - - smtp
+relay unix - - n - - smtp
+# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
+showq unix n - n - - showq
+error unix - - n - - error
+retry unix - - n - - error
+discard unix - - n - - discard
+local unix - n n - - local
+virtual unix - n n - - virtual
+lmtp unix - - n - - lmtp
+anvil unix - - n - 1 anvil
+scache unix - - n - 1 scache
+#
+# ====================================================================
+# Interfaces to non-Postfix software. Be sure to examine the manual
+# pages of the non-Postfix software to find out what options it wants.
+#
+# Many of the following services use the Postfix pipe(8) delivery
+# agent. See the pipe(8) man page for information about ${recipient}
+# and other message envelope options.
+# ====================================================================
+#
+# maildrop. See the Postfix MAILDROP_README file for details.
+# Also specify in main.cf: maildrop_destination_recipient_limit=1
+#
+#maildrop unix - n n - - pipe
+# flags=DRhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient}
+#
+# ====================================================================
+#
+# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
+#
+# Specify in cyrus.conf:
+# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
+#
+# Specify in main.cf one or more of the following:
+# mailbox_transport = lmtp:inet:localhost
+# virtual_transport = lmtp:inet:localhost
+#
+# ====================================================================
+#
+# Cyrus 2.1.5 (Amos Gouaux)
+# Also specify in main.cf: cyrus_destination_recipient_limit=1
+#
+#cyrus unix - n n - - pipe
+# user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
+#
+# ====================================================================
+#
+# Old example of delivery via Cyrus.
+#
+#old-cyrus unix - n n - - pipe
+# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
+#
+# ====================================================================
+#
+# See the Postfix UUCP_README file for configuration details.
+#
+#uucp unix - n n - - pipe
+# flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
+#
+# ====================================================================
+#
+# Other external delivery methods.
+#
+#ifmail unix - n n - - pipe
+# flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
+#
+#bsmtp unix - n n - - pipe
+# flags=Fq. user=bsmtp argv=/usr/local/sbin/bsmtp -f $sender $nexthop $recipient
+#
+#scalemail-backend unix - n n - 2 pipe
+# flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store
+# ${nexthop} ${user} ${extension}
+#
+#mailman unix - n n - - pipe
+# flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
+# ${nexthop} ${user}
+
+
+{% endif %}
diff --git a/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/postfix b/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/postfix
new file mode 100644
index 000000000..cf34c5829
--- /dev/null
+++ b/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/postfix
@@ -0,0 +1,6 @@
+{% if helpers.exists('OPNsense.postfix.general.enabled') and OPNsense.postfix.general.enabled == '1' %}
+postfix_opnsense_bootup_run="/usr/local/opnsense/scripts/OPNsense/Postfix/setup.sh"
+postfix_enable="YES"
+{% else %}
+postfix_enable="NO"
+{% endif %}
diff --git a/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/transport b/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/transport
new file mode 100644
index 000000000..8c058a078
--- /dev/null
+++ b/net/postfix/src/opnsense/service/templates/OPNsense/Postfix/transport
@@ -0,0 +1,9 @@
+{% if helpers.exists('OPNsense.postfix.general.enabled') and OPNsense.postfix.general.enabled == '1' %}
+{% if helpers.exists('OPNsense.postfix.domain.domains.domain') %}
+{% for domain in helpers.toList('OPNsense.postfix.domain.domains.domain') %}
+{% if domain.enabled == '1' %}
+{{ domain.domainname }} smtp:{{ domain.destination }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% endif %}