diff --git a/README.md b/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..1257657d9e1fd0e7989f748b7dad1737afc3f004
--- /dev/null
+++ b/README.md
@@ -0,0 +1,106 @@
+Postfix
+=========
+
+This role installs Postfix and allows basic configuration.
+
+Requirements
+------------
+
+This role requires Ansible 2.4 or higher.
+
+Role Variables
+--------------
+
+| Variable | Default | Purpose |
+|----------|---------|---------|
+| postfix__recommended_packages | `[]` | Additional packages to install. These packages will have default configuration. |
+| postfix__mailname | `{{ ansible_fqdn }}` | The name of the mail system. |
+| postfix__tables | empty | Dictionaries used to build lookup tables. [Details below.](#postfix__tables) |
+| postfix__main_cf | `{}` | Used to modify or add lines in the main.cf file. [Details below.](#postfix__main_cf) |
+
+### `postfix__tables`
+
+This dictionary contains nested dictionaries that are used to build the lookup
+tables with the corresponding name. So `postfix__tables.transport` is used to
+build the transport lookup table, `postfix__tables.sasl_passwd` is used to build
+the SASL password map table, etc. Within each dictionary the 'key' is the lookup
+pattern and the 'value' is the returned value.
+
+For example, this `postfix__tables.transport` dictionary:
+
+```yaml
+postfix__tables:
+  transport:
+    'internal.domain.tld': ':'
+    '*': 'discard:'
+```
+
+would result in the following transport table:
+
+```
+internal.domain.tld :
+* discard:
+```
+
+This role currently only supports the transport lookup table and the SASL lookup
+table. More information on the transport table format can be found
+[here][transport-docs] and more information on the SASL passwords lookup table
+format can be found [here.][sasl-passwd-docs]
+
+### `postfix__main_cf`
+
+This dictionary is used to add or modify lines in the main.cf file. Each key
+corresponds to a parameter in main.cf, and the value is what the parameter
+should be set to. If the parameter already exists in the file, then that line
+will be replaced. Otherwise, a new line will be added at the end of the file.
+
+This dictionary is merged with the internal `postfix__main_cf_default`
+dictionary which defines some reasonable defaults, such as enabling
+opportunistic TLS for the SMTP client. All keys in `postfix__main_cf_default`
+can be overridden in `postfix__main_cf`.
+
+Example Playbooks
+----------------
+
+This example configures Postfix to accept mail on the loopback interface and
+relay it to Mailgun's SMTP servers. It also uses SASL + TLS to authenticate with
+Mailgun.
+
+```yaml
+- hosts: servers
+  tasks:
+    - include_role:
+        name: postfix
+      vars:
+        postfix__main_cf:
+          inet_interfaces: loopback-only
+          relayhost: '[smtp.mailgun.org]:587'
+          smtp_sasl_auth_enable: 'yes'
+          smtp_tls_security_level: encrypt
+          smtp_sasl_tls_security_options: noanonymous
+        postfix__tables:
+          sasl_passwd:
+            '[smtp.mailgun.org]:587': 'USERNAME:PASSWORD'
+```
+
+Another common configuration when doing development is to filter all mail so
+that only mail sent to your internal domain is actually sent. All other mail
+will be dropped silently to prevent accidentally sending emails when developing
+against real data. You can do that using transport maps
+
+```yaml
+- hosts: servers
+  tasks:
+    - include_role:
+        name: postfix
+      vars:
+        postfix__tables:
+          transport:
+            'internal.domain.tld': ':'
+            '*': 'discard:'
+```
+
+
+
+[transport-docs]: http://www.postfix.org/transport.5.html
+[sasl-passwd-docs]: http://www.postfix.org/SASL_README.html#client_sasl_sender
diff --git a/defaults/main.yml b/defaults/main.yml
index bc24882e0a964078cf450bd5b35d50bfe1a4dc54..bab552218f48cff784de2044ca903370e2c2e32e 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -1,71 +1,43 @@
 ---
+# Additional packages to install. These packages won't be configured.
+postfix__recommended_packages: []
+
 # The name of this mail system, set in '/etc/mailname'
 postfix__mailname: '{{ ansible_fqdn }}'
 
-
-
-# Default variables for the main.cf template. These are always included.
-postfix__myorigin:
-postfix__smtpd_banner: '$myhostname ESMTP $mail_name ({{ ansible_distribution }})'
-postfix__biff: no
-postfix__append_dot_mydomain: no
-postfix__generate_delayed_mail_warnings: no
-postfix__delay_warning_time: 4h
-postfix__readme_directory: no
-postfix__smtpd_tls_cert_file: /etc/ssl/certs/ssl-cert-snakeoil.pem
-postfix__smtpd_tls_key_file: /etc/ssl/private/ssl-cert-snakeoil.key
-postfix__smtpd_use_tls: yes
-postfix__smtpd_tls_session_cache_database: 'btree:${data_directory}/smtpd_scache'
-postfix__smtp_tls_session_cache_database: 'btree:${data_directory}/smtp_scache'
-postfix__smtpd_relay_restrictions: permit_mynetworks permit_sasl_authenticated defer_unauth_destination
-postfix__myhostname: '{{ ansible_hostname | d() }}'
-postfix__alias_maps: 'hash:/etc/aliases'
-postfix__alias_database: 'hash:/etc/aliases'
-postfix__mydestination: '$myhostname, localhost.localdomain, localhost'
-postfix__relayhost:
-postfix__mynetworks: '127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128'
-postfix__mailbox_size_limit: 0
-postfix__recipient_delimiter: '+'
-postfix__inet_interfaces: all
-postfix__inet_protocols: all
-
-
-
-# Transport map
-# <pattern> is an email address, domain name, or * to lookup the mail recipient
-# <result> specificies how and where to deliver mail and has the format
-#          <transport>:<nexthop>. Both <transport> and <nexthop> are optional,
-#          but the delimiting ':' is required.
-#
-# EXAMPLES:
-#
-# This configuration will pass mail for the domain 'internal.domain.com' without
-# modifying it, while discard all mail addressed to other recipient domains.
+# These dictionaries build the lookup tables with the corresponding name. So
+# postfix__tables.transport is used to build the transport lookup table,
+# postfix__tables.sasl_passwd is used to build the SASL password map table, etc.
+# Within each dictionary the 'key' is the lookup pattern and the 'value' is the
+# returned value.
 #
-# postfix__transport_map:
-#   - { pattern: 'internal.domain.com', result: ':' }
-#   - { pattern: '*', result: 'discard:' }
+# For example, this postfix__tables.transport example:
 #
+#     postfix__tables:
+#       transport:
+#         'codingallnight.com': ':'
+#         '*': 'discard:'
 #
-# This configuration will discard mail sent to localhost and will relay all
-# other mail through Mailgun.
+# would result in the following transport table:
 #
-# postfix__transport_map:
-#   - { pattern: 'localhost', result: 'discard:' }
-#   - { pattern: 'localhost.localdomain', result: 'discard:' }
-#   - { pattern: '*', result: 'relay:[smtp.mailgun.org]:587' }
+#     codingallnight.com :
+#     * discard:
 #
-#
-# Valid <transport> and <nexthop> values are described in the postfix transport
-# documentation. http://www.postfix.org/transport.5.html
-postfix__transport_map: []
-
+postfix__tables:
+  sasl_passwd: {}
+  transport: {}
 
+# This dictionary is used to add or modify lines in the main.cf file. Each key
+# corresponds to a parameter in main.cf, and the value is what the parameter
+# should be set to. If the parameter already exists in the file, then that line
+# will be replaced. Otherwise, a new line will be added at the end of the file.
+postfix__main_cf: {}
+# This dictionary holds the default configuration for main.cf and all of its
+# keys can overridden in the postfix__main_cf dictionary.
+postfix__main_cf_default:
+  smtp_tls_security_level: may
+  smtp_sasl_password_maps: 'hash:/etc/postfix/sasl_passwd'
+  transport_maps: 'hash:/etc/postfix/transport'
 
-# SASL Password Maps
-postfix__smtp_sasl_password_map: []
-postfix__smtp_sasl_auth_enable: yes
-postfix__smtp_sasl_security_options: noanonymous
-postfix__smtp_sasl_tls_security_options: '{{ postfix__smtp_sasl_security_options }}'
 ...
 # vi: set ts=2 sts=2 sw=2 et ft=yaml:
diff --git a/tasks/install-postfix_debian.yml b/tasks/install-postfix.debian.yml
similarity index 83%
rename from tasks/install-postfix_debian.yml
rename to tasks/install-postfix.debian.yml
index 446670bf991ffd93c6f0c81fad9b4bb5027cd00b..582b8b7a26f566678ba1d7e7bcf20f5a167bad56 100644
--- a/tasks/install-postfix_debian.yml
+++ b/tasks/install-postfix.debian.yml
@@ -13,9 +13,6 @@
     name: '{{ item }}'
     state: present
     cache_valid_time: 3600
-  with_items:
-    - postfix
-    - mailutils
-    - make
+  with_items: '{{["postfix", "make"] + postfix__recommended_packages }}'
 ...
 # vi: set ts=2 sts=2 sw=2 et ft=yaml:
diff --git a/tasks/install-postfix.fedora.yml b/tasks/install-postfix.fedora.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5a4b301918dee476c801b555799ccbafcb6d6a65
--- /dev/null
+++ b/tasks/install-postfix.fedora.yml
@@ -0,0 +1,8 @@
+---
+- name: Install Postfix and related packages
+  dnf:
+    name: '{{ item }}'
+    state: present
+  with_items: '{{["postfix", "make"] + postfix__recommended_packages }}'
+...
+# vi: set ts=2 sts=2 sw=2 et ft=yaml:
diff --git a/tasks/install-postfix.redhat.yml b/tasks/install-postfix.redhat.yml
new file mode 100644
index 0000000000000000000000000000000000000000..580fcb30bdc213a0095bb185c409cff37d1c6242
--- /dev/null
+++ b/tasks/install-postfix.redhat.yml
@@ -0,0 +1,9 @@
+---
+- name: Install Postfix and related packages
+  yum:
+    name: '{{ item }}'
+    state: present
+    update_cache: yes
+  with_items: '{{["postfix", "make"] + postfix__recommended_packages }}'
+...
+# vi: set ts=2 sts=2 sw=2 et ft=yaml:
diff --git a/tasks/main.yml b/tasks/main.yml
index 11d98561884a4539610f12d3726cb76a19b8a213..af289dbbc900c64973fb1d0fad022d40aadb02ed 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -1,22 +1,25 @@
 ---
-- include_tasks: '{{ task_file }}'
+- name: Include OS-specific variables
+  include_vars: '{{ var_file }}'
   loop_control:
-    loop_var: task_file
+    loop_var: var_file
   with_first_found:
-    - tasks/install-postfix_{{ ansible_distribution | lower }}-{{ ansible_distribution_version | lower }}.yml
-    - tasks/install-postfix_{{ ansible_distribution | lower }}-{{ ansible_distribution_release | lower }}.yml
-    - tasks/install-postfix_{{ ansible_distribution | lower }}-{{ ansible_distribution_major_version | lower }}.yml
-    - tasks/install-postfix_{{ ansible_distribution | lower }}.yml
-    - tasks/install-postfix_{{ ansible_os_family | lower }}.yml
+    - vars/{{ ansible_distribution | lower }}-{{ ansible_distribution_version | lower }}.yml
+    - vars/{{ ansible_distribution | lower }}-{{ ansible_distribution_release | lower }}.yml
+    - vars/{{ ansible_distribution | lower }}-{{ ansible_distribution_major_version | lower }}.yml
+    - vars/{{ ansible_distribution | lower }}.yml
+    - vars/{{ ansible_os_family | lower }}.yml
 
-- name: Generate Postfix 'main.cf' configuration
-  template:
-    src: templates/main.cf.j2
-    dest: /etc/postfix/main.cf
-    owner: root
-    group: root
-    mode: 0644
-  notify: ['reload postfix']
+- name: Include OS-specific tasks
+  include_tasks: '{{ task_file }}'
+  loop_control:
+    loop_var: task_file
+  with_first_found:
+    - tasks/install-postfix.{{ ansible_distribution | lower }}-{{ ansible_distribution_version | lower }}.yml
+    - tasks/install-postfix.{{ ansible_distribution | lower }}-{{ ansible_distribution_release | lower }}.yml
+    - tasks/install-postfix.{{ ansible_distribution | lower }}-{{ ansible_distribution_major_version | lower }}.yml
+    - tasks/install-postfix.{{ ansible_distribution | lower }}.yml
+    - tasks/install-postfix.{{ ansible_os_family | lower }}.yml
 
 - name: Place the Postfix makefile
   template:
@@ -26,24 +29,50 @@
     group: root
     mode: 0644
 
-- name: Generate Postfix sasl_passwd map
+- name: Create the SASL password lookup table
   template:
-    src: templates/sasl_passwd.in.j2
+    src: lookup_table.j2
     dest: /etc/postfix/sasl_passwd.in
     owner: root
     group: root
     mode: 0600
-  when: postfix__smtp_sasl_password_map
+  vars:
+    table: '{{ postfix__tables.sasl_passwd }}'
   notify: ['make postfix sasl_passwd.db']
 
-- name: Generate Postfix transport map
+- name: Create the transport lookup table
   template:
-    src: templates/transport.in.j2
+    src: lookup_table.j2
     dest: /etc/postfix/transport.in
     owner: root
     group: root
     mode: 0644
-  when: postfix__transport_map
+  vars:
+    table: '{{ postfix__tables.transport }}'
   notify: ['make postfix transport.db']
+
+- name: Mark the 'main.cf' file as being managed by Ansible
+  lineinfile:
+    path: /etc/postfix/main.cf
+    insertbefore: BOF
+    state: present
+    line: "# This file is managed by Ansible, changes will be overwritten\n"
+    regexp: '^# This file is managed by Ansible'
+
+- name: Merge the main_cf dictionaries
+  set_fact:
+    __postfix__main_cf_merged: '{{ postfix__main_cf_default | combine(postfix__main_cf, recursive=True) }}'
+
+#- debug:
+#    var: __postfix__main_cf_merged
+
+- name: Configure the Postfix 'main.cf' file
+  lineinfile:
+    path: /etc/postfix/main.cf
+    line: '{{ item.key }} = {{ item.value }}'
+    regexp: '^\s*{{ item.key }}\s*='
+    state: present
+  with_dict: '{{ __postfix__main_cf_merged }}'
+  notify: ['reload postfix']
 ...
 # vi: set ts=2 sts=2 sw=2 et ft=yaml: 
diff --git a/templates/lookup_table.j2 b/templates/lookup_table.j2
new file mode 100644
index 0000000000000000000000000000000000000000..43d3c79bed5d2fc0d9cc3cb6b47deaf90d2823c2
--- /dev/null
+++ b/templates/lookup_table.j2
@@ -0,0 +1,5 @@
+# {{ ansible_managed }}
+
+{% for key, value in table.items() %}
+{{ key }} {{ value }}
+{% endfor %}
diff --git a/templates/sasl_passwd.in.j2 b/templates/sasl_passwd.in.j2
deleted file mode 100644
index eb2cb968464c6e09a33297c6fafabe61d48914c5..0000000000000000000000000000000000000000
--- a/templates/sasl_passwd.in.j2
+++ /dev/null
@@ -1,5 +0,0 @@
-# {{ ansible_managed }}
-
-{% for item in postfix__smtp_sasl_password_map %}
-{{ item.lookup }} {{ item.credentials }}
-{% endfor %}
diff --git a/templates/transport.in.j2 b/templates/transport.in.j2
deleted file mode 100644
index 715d8a2fcaa0aaa01b7464bf195e36c9c345baa7..0000000000000000000000000000000000000000
--- a/templates/transport.in.j2
+++ /dev/null
@@ -1,5 +0,0 @@
-# {{ ansible_managed }}
-
-{% for item in postfix__transport_map %}
-{{ item.pattern }} {{ item.result }}
-{% endfor %}
diff --git a/vars/debian.yml b/vars/debian.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5589c1cfce337b9aa6f7d11b79d750349f5e66d0
--- /dev/null
+++ b/vars/debian.yml
@@ -0,0 +1,8 @@
+---
+# This file overrides variables set in defaults/main.yml.
+# Full documentation of the variables is available in the file.
+
+postfix__recommended_packages:
+  - mailutils
+...
+# vi: set ts=2 sts=2 sw=2 et ft=yaml:
diff --git a/vars/redhat.yml b/vars/redhat.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e64316c11f357dafaa2a624dc21710d02611e278
--- /dev/null
+++ b/vars/redhat.yml
@@ -0,0 +1,9 @@
+---
+# This file overrides variables set in defaults/main.yml.
+# Full documentation of the variables is available in the file.
+
+postfix__recommended_packages:
+  - cyrus-sasl-plain # Most third-party mail servers support PLAIN auth
+  - mailx
+...
+# vi: set ts=2 sts=2 sw=2 et ft=yaml: