[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-90bafa46-b789-4b81-8591-a03e5f529097":3},{"id":4,"body":5,"uuid":6,"created_at":7,"updated_at":8,"brand":9,"header":10,"short_body":11,"image":12,"published":13,"published_at":14,"tags":15},31,"\u003Cp>Your application needs to receive email.\u003C\u002Fp>\n\u003Cp>Maybe you are building a support platform, processing invoices, tracking customer replies, or creating a unique email address for every project in your SaaS.\u003C\u002Fp>\n\u003Cp>Normally, receiving email means running a mail transfer agent, exposing port 25, configuring MX records, handling MIME messages, validating senders, storing attachments, and monitoring the server.\u003C\u002Fp>\n\u003Cp>That becomes a problem when your hosting provider blocks SMTP traffic—or when your engineering team simply does not want to operate an internet-facing mail server.\u003C\u002Fp>\n\u003Cp>The practical alternative is to separate \u003Cstrong>email transport\u003C\u002Fstrong> from \u003Cstrong>application processing\u003C\u002Fstrong>:\u003C\u002Fp>\n\u003Col>\n\u003Cli>An inbound email provider accepts the SMTP connection.\u003C\u002Fli>\n\u003Cli>The provider parses and stores the message.\u003C\u002Fli>\n\u003Cli>Your application receives the email as a signed HTTPS webhook.\u003C\u002Fli>\n\u003Cli>Your code processes the message like any other API event.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Postwing’s inbound email feature implements this architecture. You point a receiving hostname at Postwing, create address routes, and receive parsed messages through \u003Ccode>inbound.received\u003C\u002Fcode> webhook events.\u003C\u002Fp>\n\u003Ch2>Why blocked SMTP ports cause problems\u003C\u002Fh2>\n\u003Cp>Before choosing a workaround, it is important to distinguish between the common SMTP ports.\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Port\u003C\u002Fth>\n\u003Cth>Primary purpose\u003C\u002Fth>\n\u003Cth>Does it receive public internet email?\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>25\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Server-to-server SMTP relay\u003C\u002Ftd>\n\u003Ctd>Yes\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>587\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Authenticated message submission\u003C\u002Ftd>\n\u003Ctd>No\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>465\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Message submission over implicit TLS\u003C\u002Ftd>\n\u003Ctd>No\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>Port 25 remains the standard port used when one mail server transfers a message to another. Port 587 is intended for message submission, while port 465 is registered for message submission over TLS. Replacing inbound port 25 with port 587 or 465 does not make your server a normal public MX receiver.\u003C\u002Fp>\n\u003Cp>Cloud restrictions vary:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Amazon EC2 restricts outbound port 25 traffic to public addresses by default.\u003C\u002Fli>\n\u003Cli>Google Cloud generally blocks external egress to port 25 but allows ports 465 and 587 unless your own firewall rules block them.\u003C\u002Fli>\n\u003Cli>Azure blocks outbound port 25 for many subscription and platform configurations and recommends authenticated relay services.\u003C\u002Fli>\n\u003Cli>DigitalOcean currently blocks SMTP ports 25, 465, and 587 on Droplets.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Many of these restrictions focus on outbound traffic. However, receiving email yourself still requires an internet-reachable SMTP service, correct firewall rules, a stable public IP, DNS configuration, TLS management, abuse controls, storage, and ongoing server maintenance.\u003C\u002Fp>\n\u003Cp>For most SaaS products, removing SMTP from the application infrastructure is simpler than trying to negotiate port exemptions and operate a mail server.\u003C\u002Fp>\n\u003Ch2>The better architecture: MX in, HTTPS out\u003C\u002Fh2>\n\u003Cp>An inbound email service acts as a bridge between the email network and your application.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">Sender's mail server\n        |\n        | SMTP on port 25\n        v\nPostwing inbound MX\n        |\n        | Parse, authenticate and store\n        v\nSigned HTTPS webhook\n        |\n        v\nYour application\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Your server only needs to accept normal HTTPS traffic. Postwing handles the SMTP-facing side of the system and delivers a structured JSON event to your webhook endpoint.\u003C\u002Fp>\n\u003Cp>This means your application does not need to:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Expose port 25\u003C\u002Fli>\n\u003Cli>Install Postfix, Exim, or another mail transfer agent\u003C\u002Fli>\n\u003Cli>Parse multipart MIME messages\u003C\u002Fli>\n\u003Cli>Maintain an SMTP queue\u003C\u002Fli>\n\u003Cli>Store raw messages and attachments itself\u003C\u002Fli>\n\u003Cli>Implement SMTP-level address rejection\u003C\u002Fli>\n\u003Cli>Keep a mail server IP off blocklists\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>You work with HTTP requests instead of SMTP sessions.\u003C\u002Fp>\n\u003Ch2>How Postwing inbound email works\u003C\u002Fh2>\n\u003Cp>Postwing inbound email is designed for application workflows rather than human mailboxes.\u003C\u002Fp>\n\u003Cp>It does not provide IMAP or POP3 access. Instead, every accepted email is parsed and sent to your code as an \u003Ccode>inbound.received\u003C\u002Fcode> webhook. The event includes:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>the inbound message ID and the receiving domain\u003C\u002Fli>\n\u003Cli>the matched route and the accepted recipient\u003C\u002Fli>\n\u003Cli>the SMTP sender (\u003Ccode>mail_from\u003C\u002Fcode>) and the parsed \u003Ccode>From\u003C\u002Fcode> header\u003C\u002Fli>\n\u003Cli>the \u003Ccode>To\u003C\u002Fcode> and \u003Ccode>Cc\u003C\u002Fcode> header addresses\u003C\u002Fli>\n\u003Cli>the subject, text body, and HTML body\u003C\u002Fli>\n\u003Cli>selected headers (\u003Ccode>Date\u003C\u002Fcode>, \u003Ccode>Reply-To\u003C\u002Fcode>, \u003Ccode>In-Reply-To\u003C\u002Fcode>, \u003Ccode>References\u003C\u002Fcode>, \u003Ccode>Auto-Submitted\u003C\u002Fcode>, \u003Ccode>List-Id\u003C\u002Fcode>, and others)\u003C\u002Fli>\n\u003Cli>DKIM authentication results\u003C\u002Fli>\n\u003Cli>the message size\u003C\u002Fli>\n\u003Cli>attachment metadata\u003C\u002Fli>\n\u003Cli>API links for downloading each attachment and the raw \u003Ccode>.eml\u003C\u002Fcode> file\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Inbound email is a paid-plan feature: the domain’s plan must have both webhooks and inbound routes enabled. Paid plans allow up to 25 routes per domain by default.\u003C\u002Fp>\n\u003Cp>Here is the setup process.\u003C\u002Fp>\n\u003Ch2>Step 1: Choose a receiving hostname\u003C\u002Fh2>\n\u003Cp>Start with a subdomain such as:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">inbound.example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>A subdomain is usually the better choice than the root domain.\u003C\u002Fp>\n\u003Cp>For example, suppose your company already receives employee email at:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">alex@example.com\nsupport@example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Changing the MX records for \u003Ccode>example.com\u003C\u002Fcode> could interfere with that existing email service. A dedicated subdomain isolates application email:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">ticket-123@inbound.example.com\nreply-456@inbound.example.com\nupload@inbound.example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The receiving hostname can be a subdomain of your verified domain, or the verified domain itself. If the domain was connected specifically for receiving—\u003Ccode>help.example.com\u003C\u002Fcode>, for instance—you do not need to add a second label.\u003C\u002Fp>\n\u003Cp>There is one guard: if another provider’s MX record is already published on the domain itself, Postwing refuses it as a receiving hostname and suggests a subdomain instead. That prevents accidentally taking over mail that already flows.\u003C\u002Fp>\n\u003Ch2>Step 2: Publish the MX record\u003C\u002Fh2>\n\u003Cp>Postwing displays the exact DNS record required for your receiving hostname. A typical record looks like this:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-dns\">inbound.example.com.  IN  MX  10  mx.postwing.app.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>This record tells sending mail servers to deliver messages for \u003Ccode>@inbound.example.com\u003C\u002Fcode> to Postwing.\u003C\u002Fp>\n\u003Cp>Three conditions turn receiving on:\u003C\u002Fp>\n\u003Col>\n\u003Cli>The domain is verified through the normal DNS record check (DKIM above all).\u003C\u002Fli>\n\u003Cli>The MX record for the receiving hostname is published and confirmed by Postwing’s DNS check.\u003C\u002Fli>\n\u003Cli>The domain’s plan includes inbound email.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Verification runs automatically. Once the record is confirmed, acceptance begins within a few minutes.\u003C\u002Fp>\n\u003Cp>Postwing recommends making its MX record the only MX record for the receiving hostname.\u003C\u002Fp>\n\u003Cp>You do not need to point your main domain’s mail traffic at Postwing.\u003C\u002Fp>\n\u003Ch2>Step 3: Create a webhook endpoint\u003C\u002Fh2>\n\u003Cp>Create an HTTPS endpoint in your application, for example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">POST \u002Fwebhooks\u002Fpostwing\u002Finbound\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Then add the endpoint in the Postwing dashboard and explicitly subscribe it to:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">inbound.received\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Inbound events are opt-in. Creating a general webhook endpoint without selecting \u003Ccode>inbound.received\u003C\u002Fcode> will not deliver incoming messages to it—and unlike sending events, an inbound message is not fanned out to every subscribed endpoint. It goes to the single endpoint named by the route that matched (see the next step).\u003C\u002Fp>\n\u003Cp>Postwing signs webhook requests using HMAC-SHA256. Your application should verify the signature over the original request body before parsing or processing the event.\u003C\u002Fp>\n\u003Cp>Every request carries these headers:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">X-Webhook-Event      inbound.received\nX-Webhook-Delivery   delivery identifier, the same value as event_id in the body\nX-Webhook-Timestamp  signing time, unix seconds\nX-Webhook-Signature  HMAC-SHA256, hex encoded\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Step 4: Define inbound routes\u003C\u002Fh2>\n\u003Cp>Routes determine which addresses Postwing accepts and where their messages are delivered.\u003C\u002Fp>\n\u003Cp>Postwing supports three useful route patterns:\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Pattern\u003C\u002Fth>\n\u003Cth>Type\u003C\u002Fth>\n\u003Cth>Example matches\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>support\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Exact\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>support@inbound.example.com\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>ticket-*\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Prefix\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>ticket-91@inbound.example.com\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>*\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>Catch-all\u003C\u002Ftd>\n\u003Ctd>Any address not matched by another route\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>Pattern rules:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>A pattern is the local part only—no \u003Ccode>@\u003C\u002Fcode>, no hostname.\u003C\u002Fli>\n\u003Cli>At most one \u003Ccode>*\u003C\u002Fcode>, and only at the end.\u003C\u002Fli>\n\u003Cli>A prefix route requires at least one character where the star is: \u003Ccode>ticket-*\u003C\u002Fcode> matches \u003Ccode>ticket-91\u003C\u002Fcode> but not \u003Ccode>ticket-\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>Matching is case-insensitive.\u003C\u002Fli>\n\u003Cli>Each route points at exactly one webhook endpoint belonging to the same domain.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Exact routes have priority, followed by the longest matching prefix and then the catch-all route.\u003C\u002Fp>\n\u003Cp>An email sent to an address that does not match an enabled route is rejected inside the same SMTP transaction: mail for a hostname Postwing does not receive on is refused at \u003Ccode>RCPT TO\u003C\u002Fcode>, and a known hostname with no matching route is refused with \u003Ccode>550\u003C\u002Fcode> after the message data. Either way the sending server generates the bounce, and nothing reaches your application. Postwing does not silently accept mail for undefined addresses.\u003C\u002Fp>\n\u003Cp>This lets you create controlled address namespaces such as:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">support@inbound.example.com\ninvoice@inbound.example.com\nticket-&lt;ticket-id&gt;@inbound.example.com\nproject-&lt;project-id&gt;@inbound.example.com\ncustomer-&lt;customer-id&gt;@inbound.example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Step 5: Process the inbound webhook\u003C\u002Fh2>\n\u003Cp>A simplified inbound event contains fields such as:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-json\">{\n  &quot;event_id&quot;: &quot;6f1d2c30-0004-4c7a-9b21-0c8e5a3d7f44&quot;,\n  &quot;event&quot;: &quot;inbound.received&quot;,\n  &quot;inbound_id&quot;: &quot;0f0b6c1a-4a1e-4a3a-9f7a-2c9d1b5e8a10&quot;,\n  &quot;domain&quot;: &quot;example.com&quot;,\n  &quot;route&quot;: &quot;support&quot;,\n  &quot;recipient&quot;: &quot;support@inbound.example.com&quot;,\n  &quot;mail_from&quot;: &quot;alex@customer.example&quot;,\n  &quot;from&quot;: {\n    &quot;email&quot;: &quot;alex@customer.example&quot;,\n    &quot;name&quot;: &quot;Alex&quot;\n  },\n  &quot;to&quot;: [&quot;support@inbound.example.com&quot;],\n  &quot;cc&quot;: [],\n  &quot;subject&quot;: &quot;Problem importing my data&quot;,\n  &quot;text&quot;: &quot;The import stops at 72%.&quot;,\n  &quot;html&quot;: &quot;&lt;p&gt;The import stops at 72%.&lt;\u002Fp&gt;&quot;,\n  &quot;headers&quot;: {\n    &quot;Date&quot;: &quot;Tue, 12 Aug 2025 10:04:11 +0300&quot;,\n    &quot;Reply-To&quot;: &quot;alex@customer.example&quot;\n  },\n  &quot;message_id&quot;: &quot;&lt;a1b2c3@customer.example&gt;&quot;,\n  &quot;auth&quot;: {\n    &quot;dkim&quot;: &quot;pass&quot;,\n    &quot;dkim_aligned&quot;: true\n  },\n  &quot;size&quot;: 18422,\n  &quot;raw_url&quot;: &quot;https:\u002F\u002Fapi.postwing.app\u002Fapi\u002Finbound\u002Fmessages\u002F0f0b6c1a-...\u002Fraw\u002F&quot;,\n  &quot;attachments&quot;: [],\n  &quot;timestamp&quot;: &quot;2025-08-12T07:04:12.481Z&quot;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>raw_url\u003C\u002Fcode> and each \u003Ccode>attachments[].url\u003C\u002Fcode> are stable Postwing API paths, not pre-signed storage links. That is deliberate: the event body is stored and replayed by retries for up to 24 hours, while a signed storage URL expires in minutes. You call the API path with your account credentials, and it redirects to a freshly signed, short-lived download URL.\u003C\u002Fp>\n\u003Ch3>Node.js webhook example\u003C\u002Fh3>\n\u003Cp>The following Express endpoint verifies the Postwing signature and accepts inbound events:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">const crypto = require(&quot;crypto&quot;);\nconst express = require(&quot;express&quot;);\n\nconst app = express();\n\nconst webhookSecret = process.env.POSTWING_WEBHOOK_SECRET;\nconst replayToleranceSeconds = 300;\n\napp.post(\n  &quot;\u002Fwebhooks\u002Fpostwing\u002Finbound&quot;,\n  express.raw({ type: &quot;application\u002Fjson&quot; }),\n  async (req, res) =&gt; {\n    const signature = req.get(&quot;X-Webhook-Signature&quot;);\n    const timestamp = req.get(&quot;X-Webhook-Timestamp&quot;);\n\n    if (\n      !signature ||\n      !timestamp ||\n      !\u002F^[a-f0-9]{64}$\u002Fi.test(signature)\n    ) {\n      return res.sendStatus(400);\n    }\n\n    const timestampNumber = Number(timestamp);\n\n    if (\n      !Number.isFinite(timestampNumber) ||\n      Math.abs(Date.now() \u002F 1000 - timestampNumber) &gt;\n        replayToleranceSeconds\n    ) {\n      return res.sendStatus(400);\n    }\n\n    \u002F\u002F The signature covers:\n    \u002F\u002F &lt;timestamp&gt;.&lt;exact raw request body&gt;\n    const signedPayload = Buffer.concat([\n      Buffer.from(`${timestamp}.`, &quot;utf8&quot;),\n      req.body\n    ]);\n\n    const expectedSignature = crypto\n      .createHmac(&quot;sha256&quot;, webhookSecret)\n      .update(signedPayload)\n      .digest();\n\n    const providedSignature = Buffer.from(signature, &quot;hex&quot;);\n\n    if (\n      providedSignature.length !== expectedSignature.length ||\n      !crypto.timingSafeEqual(\n        providedSignature,\n        expectedSignature\n      )\n    ) {\n      return res.sendStatus(400);\n    }\n\n    let event;\n\n    try {\n      event = JSON.parse(req.body.toString(&quot;utf8&quot;));\n    } catch {\n      return res.sendStatus(400);\n    }\n\n    if (event.event !== &quot;inbound.received&quot;) {\n      return res.sendStatus(204);\n    }\n\n    \u002F\u002F Queue this operation idempotently.\n    \u002F\u002F event.event_id remains the same across webhook retries.\n    await enqueueInboundEmail({\n      idempotencyKey: event.event_id,\n      route: event.route,\n      recipient: event.recipient,\n      sender: event.from?.email,\n      subject: event.subject,\n      text: event.text,\n      html: event.html,\n      attachments: event.attachments,\n      senderAuthenticated:\n        event.auth?.dkim === &quot;pass&quot; &amp;&amp;\n        event.auth?.dkim_aligned === true\n    });\n\n    return res.sendStatus(202);\n  }\n);\n\napp.listen(3000);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Always calculate the signature from the exact raw request bytes. Parsing the body and serializing it again can change whitespace or formatting and invalidate the signature.\u003C\u002Fp>\n\u003Cp>Postwing expects a \u003Ccode>2xx\u003C\u002Fcode> response for successful delivery, and allows 10 seconds for it. Webhook handlers should verify and enqueue the event quickly rather than performing slow attachment processing or AI analysis before responding.\u003C\u002Fp>\n\u003Cp>Failed deliveries are retried on a fixed backoff: after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, and 24 hours. Once those attempts are exhausted the delivery is marked failed, but the message itself remains available through the API and the dashboard.\u003C\u002Fp>\n\u003Ch2>Inbound limits\u003C\u002Fh2>\n\u003Cp>Defaults, per message and per domain:\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Limit\u003C\u002Fth>\n\u003Cth>Value\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Maximum message size\u003C\u002Ftd>\n\u003Ctd>30 MB\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Attachments per message\u003C\u002Ftd>\n\u003Ctd>up to 25\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Size per attachment\u003C\u002Ftd>\n\u003Ctd>up to 25 MB\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Stored text and HTML bodies\u003C\u002Ftd>\n\u003Ctd>up to 500,000 chars\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>Throughput per domain\u003C\u002Ftd>\n\u003Ctd>500 messages\u002Fhour\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>An oversized message is refused at the SMTP layer. When the hourly limit is exceeded, Postwing answers \u003Ccode>450\u003C\u002Fcode> and the sending server retries later. Bodies longer than the stored limit are truncated in the payload; the verbatim \u003Ccode>.eml\u003C\u002Fcode> always keeps the whole message.\u003C\u002Fp>\n\u003Cp>Received messages and their stored files are deleted according to your plan’s log retention period.\u003C\u002Fp>\n\u003Ch2>What can you build with inbound email?\u003C\u002Fh2>\n\u003Cp>Receiving email as structured data supports considerably more than a contact form.\u003C\u002Fp>\n\u003Ch3>1. Email-to-ticket support system\u003C\u002Fh3>\n\u003Cp>Create an exact route:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">support@inbound.example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>When a customer sends an email:\u003C\u002Fp>\n\u003Col>\n\u003Cli>Postwing accepts and parses it.\u003C\u002Fli>\n\u003Cli>Your webhook receives the message.\u003C\u002Fli>\n\u003Cli>Your application looks up the customer by sender address.\u003C\u002Fli>\n\u003Cli>A support ticket is created.\u003C\u002Fli>\n\u003Cli>Attachments are associated with the ticket.\u003C\u002Fli>\n\u003Cli>Your application sends a confirmation through the normal Postwing sending API.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>This gives customers a familiar support channel while keeping the workflow inside your SaaS.\u003C\u002Fp>\n\u003Ch3>2. Reply-to-ticket addresses\u003C\u002Fh3>\n\u003Cp>Assign each ticket a unique address:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">ticket-8942@inbound.example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Create a prefix route for:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">ticket-*\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Your webhook can extract \u003Ccode>8942\u003C\u002Fcode> from the recipient and attach the message to the correct ticket.\u003C\u002Fp>\n\u003Cp>This eliminates fragile subject-line matching. The routing identifier is part of the delivery address itself.\u003C\u002Fp>\n\u003Cp>The same pattern works for:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">thread-&lt;id&gt;@inbound.example.com\nconversation-&lt;id&gt;@inbound.example.com\ncase-&lt;id&gt;@inbound.example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>3. Reply-by-email notifications\u003C\u002Fh3>\n\u003Cp>Suppose your project-management SaaS sends this notification:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">Maria mentioned you in Project Apollo.\nReply to this email to add a comment.\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Set the message’s reply address to:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">project-41-thread-987@inbound.example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>When the user replies, your webhook identifies the project and thread from the recipient address. The email body becomes a new comment.\u003C\u002Fp>\n\u003Cp>Before saving the comment, your application can:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Remove quoted reply history\u003C\u002Fli>\n\u003Cli>Remove signatures\u003C\u002Fli>\n\u003Cli>Check whether the sender belongs to the project\u003C\u002Fli>\n\u003Cli>Detect automated replies\u003C\u002Fli>\n\u003Cli>Preserve attached files\u003C\u002Fli>\n\u003Cli>Store the original \u003Ccode>.eml\u003C\u002Fcode> for auditing\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>4. Invoice and document intake\u003C\u002Fh3>\n\u003Cp>Create an address such as:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">invoices@inbound.example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Customers or suppliers can email invoices directly to your system.\u003C\u002Fp>\n\u003Cp>Your application can then:\u003C\u002Fp>\n\u003Col>\n\u003Cli>Confirm that the message contains an attachment.\u003C\u002Fli>\n\u003Cli>Download the attachment through the authenticated Postwing API path.\u003C\u002Fli>\n\u003Cli>Scan the file for malware.\u003C\u002Fli>\n\u003Cli>Extract invoice fields.\u003C\u002Fli>\n\u003Cli>Match the sender to a supplier.\u003C\u002Fli>\n\u003Cli>Create an approval workflow.\u003C\u002Fli>\n\u003Cli>Store the raw email as an audit record.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Postwing’s inbound payload identifies each attachment’s ID, filename, MIME type, size, inline status, content ID, and API download path.\u003C\u002Fp>\n\u003Ch3>5. Per-customer ingestion addresses\u003C\u002Fh3>\n\u003Cp>Generate a unique address for every customer:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">customer-a83f9@inbound.example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>That address can become a simple integration surface. Instead of building an API integration, the customer forwards reports, alerts, receipts, or exported files to their assigned email address.\u003C\u002Fp>\n\u003Cp>Your application knows which tenant owns the message from the recipient identifier.\u003C\u002Fp>\n\u003Cp>This is useful for:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Expense-management platforms\u003C\u002Fli>\n\u003Cli>Bookkeeping applications\u003C\u002Fli>\n\u003Cli>Compliance archives\u003C\u002Fli>\n\u003Cli>Logistics systems\u003C\u002Fli>\n\u003Cli>Property-management software\u003C\u002Fli>\n\u003Cli>Recruitment platforms\u003C\u002Fli>\n\u003Cli>Document-processing products\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Use opaque, non-sequential identifiers when addresses expose tenant or object IDs.\u003C\u002Fp>\n\u003Ch3>6. CRM lead capture\u003C\u002Fh3>\n\u003Cp>Assign an email address to each campaign, partner, or sales representative:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">partner-acme@inbound.example.com\ncampaign-berlin@inbound.example.com\nrep-42@inbound.example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Incoming messages can automatically create leads and associate them with the correct acquisition source.\u003C\u002Fp>\n\u003Cp>A catch-all route can support dynamically generated addresses, while exact routes can reserve sensitive or operational names.\u003C\u002Fp>\n\u003Ch3>7. Automated report processing\u003C\u002Fh3>\n\u003Cp>Many legacy systems can send email but cannot call a modern REST API.\u003C\u002Fp>\n\u003Cp>Instead of building a custom integration, give the system an inbound address:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">reports-warehouse-7@inbound.example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The legacy service sends its scheduled report by email. Your webhook receives the message, downloads the file, validates it, and imports the data.\u003C\u002Fp>\n\u003Cp>Email becomes an adapter between an older system and your application.\u003C\u002Fp>\n\u003Ch3>8. AI-assisted email workflows\u003C\u002Fh3>\n\u003Cp>Because inbound messages arrive as structured JSON, you can pass selected content into an automated classification or extraction pipeline.\u003C\u002Fp>\n\u003Cp>Examples include:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Classifying support requests\u003C\u002Fli>\n\u003Cli>Extracting purchase-order numbers\u003C\u002Fli>\n\u003Cli>Detecting customer intent\u003C\u002Fli>\n\u003Cli>Summarizing long correspondence\u003C\u002Fli>\n\u003Cli>Routing messages to the correct team\u003C\u002Fli>\n\u003Cli>Drafting suggested replies\u003C\u002Fli>\n\u003Cli>Identifying missing invoice fields\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Keep the webhook handler fast. Queue the message first and run expensive processing in a worker.\u003C\u002Fp>\n\u003Cp>Treat email bodies and attachments as untrusted input. They may contain prompt-injection attempts, malicious documents, tracking content, or misleading instructions.\u003C\u002Fp>\n\u003Ch2>Security and reliability checklist\u003C\u002Fh2>\n\u003Cp>Moving SMTP outside your infrastructure reduces operational work, but your application still needs a secure webhook implementation.\u003C\u002Fp>\n\u003Ch3>Verify every webhook signature\u003C\u002Fh3>\n\u003Cp>Do not trust a request simply because it reached an obscure endpoint.\u003C\u002Fp>\n\u003Cp>Validate:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>X-Webhook-Signature\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>X-Webhook-Timestamp\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>The exact raw body\u003C\u002Fli>\n\u003Cli>A reasonable replay-protection window\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Use a constant-time comparison for the computed and supplied signatures.\u003C\u002Fp>\n\u003Ch3>Deduplicate with \u003Ccode>event_id\u003C\u002Fcode>\u003C\u002Fh3>\n\u003Cp>Inbound delivery is at least once. A webhook may be retried when your endpoint times out or returns an error.\u003C\u002Fp>\n\u003Cp>Store \u003Ccode>event_id\u003C\u002Fcode> as an idempotency key so the same event cannot create multiple tickets, comments, invoices, or leads. The value does not change across retries.\u003C\u002Fp>\n\u003Ch3>Do not trust the visible sender automatically\u003C\u002Fh3>\n\u003Cp>The \u003Ccode>From\u003C\u002Fcode> header can be forged.\u003C\u002Fp>\n\u003Cp>Postwing reports the DKIM result and whether the signing domain aligns with the visible \u003Ccode>From\u003C\u002Fcode> domain. \u003Ccode>auth.dkim\u003C\u002Fcode> has five values:\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Value\u003C\u002Fth>\n\u003Cth>Meaning\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>\u003Ccode>pass\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A signature verified\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>fail\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>A signature did not verify\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>none\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>No signature at all—normal for plenty of legitimate mail\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>temperror\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>DNS did not answer, so nothing could be checked\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>permerror\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd>The signature or its key is malformed\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>\u003Ccode>temperror\u003C\u002Fcode> and \u003Ccode>permerror\u003C\u002Fcode> are kept separate from \u003Ccode>fail\u003C\u002Fcode> on purpose: “this message is forged” and “we could not check” are different facts, and a rule that drops everything except \u003Ccode>pass\u003C\u002Fcode> should account for both.\u003C\u002Fp>\n\u003Cp>There is no \u003Ccode>spf\u003C\u002Fcode> field and no \u003Ccode>dmarc\u003C\u002Fcode> field in the payload, also by design. SPF is not evaluated in this path, and deriving a DMARC verdict from DKIM alone would produce false failures for SPF-aligned mail.\u003C\u002Fp>\n\u003Cp>For workflows that require stronger sender confidence, treat a sender as authenticated only when:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-javascript\">event.auth.dkim === &quot;pass&quot; &amp;&amp;\nevent.auth.dkim_aligned === true\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Even then, authorization remains your application’s responsibility. A correctly authenticated sender is not automatically allowed to modify every project, ticket, or account.\u003C\u002Fp>\n\u003Ch3>Authorize from the accepted recipient\u003C\u002Fh3>\n\u003Cp>Use the \u003Ccode>recipient\u003C\u002Fcode> and matched \u003Ccode>route\u003C\u002Fcode> to determine where the message belongs.\u003C\u002Fp>\n\u003Cp>Do not treat every address in the message’s visible \u003Ccode>To\u003C\u002Fcode> or \u003Ccode>Cc\u003C\u002Fcode> headers as an address Postwing accepted. Header recipient lists can include unrelated or manipulated values.\u003C\u002Fp>\n\u003Ch3>Validate attachments\u003C\u002Fh3>\n\u003Cp>Before processing an attachment:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Enforce an application-level size limit\u003C\u002Fli>\n\u003Cli>Allow only required file formats\u003C\u002Fli>\n\u003Cli>Check the real file signature, not only the MIME type\u003C\u002Fli>\n\u003Cli>Scan files for malware\u003C\u002Fli>\n\u003Cli>Generate new storage filenames\u003C\u002Fli>\n\u003Cli>Keep files outside the public web root\u003C\u002Fli>\n\u003Cli>Avoid rendering untrusted HTML directly\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Postwing’s own limits are a maximum message size of 30 MB, up to 25 attachments, and a maximum individual attachment size of 25 MB. Your application may want stricter ones.\u003C\u002Fp>\n\u003Ch3>Prevent email loops\u003C\u002Fh3>\n\u003Cp>Be careful with automatic replies.\u003C\u002Fp>\n\u003Cp>Do not configure an autoresponder that sends responses back to the same inbound route. Postwing bounds a runaway loop by counting \u003Ccode>Received:\u003C\u002Fcode> hops and by the per-domain hourly limit, but that is a backstop against growth, not a substitute for correct autoresponder logic.\u003C\u002Fp>\n\u003Cp>Automated messages and mailing-list traffic are not discarded for you—that is your mail, and the decision is your application’s. Inspect headers such as \u003Ccode>Auto-Submitted\u003C\u002Fcode>, \u003Ccode>Precedence\u003C\u002Fcode>, \u003Ccode>In-Reply-To\u003C\u002Fcode>, and \u003Ccode>List-Id\u003C\u002Fcode>; they are included in the event.\u003C\u002Fp>\n\u003Ch2>Inbound email is not a mailbox\u003C\u002Fh2>\n\u003Cp>Postwing inbound email is intended to deliver mail to code.\u003C\u002Fp>\n\u003Cp>It is not a replacement for Gmail, Outlook, or a shared support mailbox used directly by employees. There is no IMAP or POP3 interface, and users do not sign in to read or reply to messages.\u003C\u002Fp>\n\u003Cp>Your application owns the user experience.\u003C\u002Fp>\n\u003Cp>For example:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>A support SaaS displays inbound messages in a ticket timeline.\u003C\u002Fli>\n\u003Cli>A CRM displays them in a contact activity feed.\u003C\u002Fli>\n\u003Cli>An accounting product displays them beside an invoice.\u003C\u002Fli>\n\u003Cli>A project-management platform converts them into comments.\u003C\u002Fli>\n\u003Cli>A document platform displays them as processed uploads.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Replies can be sent separately through Postwing’s email API, SMTP relay, or SDK.\u003C\u002Fp>\n\u003Ch2>Why use an inbound email service instead of self-hosting?\u003C\u002Fh2>\n\u003Cp>Running your own inbound SMTP server gives you complete control, but it also gives you complete responsibility.\u003C\u002Fp>\n\u003Cp>You must handle:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>SMTP listener availability\u003C\u002Fli>\n\u003Cli>MX and reverse-DNS configuration\u003C\u002Fli>\n\u003Cli>TLS certificates and protocol configuration\u003C\u002Fli>\n\u003Cli>MIME parsing edge cases\u003C\u002Fli>\n\u003Cli>Oversized messages\u003C\u002Fli>\n\u003Cli>Address validation\u003C\u002Fli>\n\u003Cli>Queue management\u003C\u002Fli>\n\u003Cli>Retries and temporary failures\u003C\u002Fli>\n\u003Cli>Spam and abuse controls\u003C\u002Fli>\n\u003Cli>Raw-message and attachment storage\u003C\u002Fli>\n\u003Cli>Security updates\u003C\u002Fli>\n\u003Cli>Monitoring and incident response\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>For an email infrastructure company, that investment may make sense.\u003C\u002Fp>\n\u003Cp>For a SaaS team whose actual goal is to turn an email into a ticket, comment, invoice, lead, or uploaded document, it usually does not.\u003C\u002Fp>\n\u003Cp>Postwing keeps SMTP at the edge and gives your application the interface developers already know how to operate: a signed JSON webhook over HTTPS.\u003C\u002Fp>\n\u003Ch2>Frequently asked questions\u003C\u002Fh2>\n\u003Ch3>Can I receive email without opening port 25?\u003C\u002Fh3>\n\u003Cp>Yes. Your application does not need to open port 25 when an inbound provider accepts email on your behalf. You point your receiving hostname’s MX record to the provider and receive messages through HTTPS webhooks.\u003C\u002Fp>\n\u003Cp>The provider still uses SMTP to communicate with sending mail servers, but that SMTP connection never reaches your application server.\u003C\u002Fp>\n\u003Ch3>Can I use port 587 instead of port 25 for inbound email?\u003C\u002Fh3>\n\u003Cp>Not for ordinary server-to-server internet delivery.\u003C\u002Fp>\n\u003Cp>Port 587 is designed for authenticated message submission by clients and applications. Mail-server relay continues to use port 25.\u003C\u002Fp>\n\u003Ch3>What about port 465?\u003C\u002Fh3>\n\u003Cp>Port 465 is used for message submission over TLS. It is not a general replacement for an MX server listening for public email delivery.\u003C\u002Fp>\n\u003Ch3>Do I need to install Postfix or Exim?\u003C\u002Fh3>\n\u003Cp>No. Postwing accepts and parses the SMTP message. Your application receives an HTTPS request containing the parsed message data.\u003C\u002Fp>\n\u003Ch3>Can I receive attachments?\u003C\u002Fh3>\n\u003Cp>Yes. Inbound webhook events include attachment metadata—filename, MIME type, size, inline status, and content ID—plus authenticated API paths for retrieving each file. The raw \u003Ccode>.eml\u003C\u002Fcode> message is also available.\u003C\u002Fp>\n\u003Cp>Defaults allow up to 25 attachments of up to 25 MB each, within a 30 MB message.\u003C\u002Fp>\n\u003Ch3>Can I create dynamic email addresses?\u003C\u002Fh3>\n\u003Cp>Yes. Prefix routes such as \u003Ccode>ticket-*\u003C\u002Fcode> can accept addresses including \u003Ccode>ticket-123@inbound.example.com\u003C\u002Fcode>. A catch-all \u003Ccode>*\u003C\u002Fcode> route can accept any address that does not match a more specific route.\u003C\u002Fp>\n\u003Ch3>What happens when my webhook is temporarily unavailable?\u003C\u002Fh3>\n\u003Cp>Postwing retries after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, and 24 hours. Because delivery is at least once, your application must deduplicate events using \u003Ccode>event_id\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>The message itself is stored either way and stays available through the API and the dashboard.\u003C\u002Fp>\n\u003Ch3>Can I receive email on my main company domain?\u003C\u002Fh3>\n\u003Cp>Technically yes—the receiving hostname may be the verified domain itself. But if that domain already carries company mail, there is no reason to replace its MX records, and Postwing refuses the configuration when it finds another provider’s MX already published there.\u003C\u002Fp>\n\u003Cp>The practical choice is a dedicated subdomain:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">inbound.example.com\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>It leaves the MX records serving \u003Ccode>example.com\u003C\u002Fcode> untouched.\u003C\u002Fp>\n\u003Ch3>Which plans include inbound email?\u003C\u002Fh3>\n\u003Cp>Inbound email is part of the paid plans; a plan needs both webhooks and inbound routes enabled. It is not available on the free plan.\u003C\u002Fp>\n\u003Ch2>Receive email without operating SMTP infrastructure\u003C\u002Fh2>\n\u003Cp>Blocked SMTP ports do not have to block your product roadmap.\u003C\u002Fp>\n\u003Cp>Instead of exposing port 25 and maintaining a mail server, let Postwing accept incoming email for a dedicated hostname. Your application receives each message as a parsed, signed HTTPS webhook that can be processed using your existing API infrastructure.\u003C\u002Fp>\n\u003Cp>The result is a simpler architecture:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">Configure MX\n    → create a route\n    → verify a webhook\n    → process incoming email\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Use inbound email to create support tickets, accept replies, ingest invoices, capture leads, process reports, or give every customer and application object its own email address.\u003C\u002Fp>","90bafa46-b789-4b81-8591-a03e5f529097","2026-08-07T11:26:04.065591+03:00","2026-08-07T11:29:33.688075+03:00","postwing","How to Receive Emails When SMTP Ports Are Blocked","You do not need to open SMTP ports or run a mail server to receive email in your application. With an inbound email service, messages are accepted for your domain, parsed, and delivered to your application through signed HTTPS webhooks.","https:\u002F\u002Fapi.postwing.app\u002Fmedia\u002Fblog\u002Frecord_90bafa46-b789-4b81-8591-a03e5f529097\u002FChatGPT_Image_Aug_7_2026_11_24_38_AM_N1otXBO.png",true,"2026-08-07T11:25:54+03:00",[16,17,18],"inbound","receiving emails","smtp"]