> For the complete documentation index, see [llms.txt](https://docs.resifactory.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.resifactory.net/imap/clients/custom-code.md).

# Your own code

Everything here uses the same connection details as any other client:

```
mail.resifactory.net : 993 , implicit TLS
username = your login email
password = an app password
```

{% hint style="success" %}
**Address messages by UID, not by sequence number.** Sequence numbers are positions in the current view and renumber when messages leave it — because of retention, or because they aged out of an [inbox window](/imap/inbox-window.md). UIDs are stable and never reused.
{% endhint %}

## Python

The standard library is enough for most jobs.

### Fetch recent mail

```python
import imaplib, email, os
from email.header import decode_header

HOST = "mail.resifactory.net"
USER = os.environ["OMS_USER"]      # your login email
PASS = os.environ["OMS_APP_PASS"]  # an app password

def decode(s):
    if not s:
        return ""
    return "".join(
        part.decode(enc or "utf-8", "replace") if isinstance(part, bytes) else part
        for part, enc in decode_header(s)
    )

with imaplib.IMAP4_SSL(HOST, 993) as m:
    m.login(USER, PASS)
    m.select("INBOX")

    # UID SEARCH; ALL is fine because the inbox window already narrows the view.
    typ, data = m.uid("SEARCH", None, "ALL")
    uids = data[0].split()

    for uid in uids[-20:]:                      # the 20 most recent
        typ, msg_data = m.uid("FETCH", uid, "(RFC822)")
        msg = email.message_from_bytes(msg_data[0][1])
        print(msg["To"], "|", decode(msg["Subject"]))
```

### Wait for a code with IDLE

`imaplib` has no IDLE support; use [`imapclient`](https://pypi.org/project/IMAPClient/).

```python
import re, os
from imapclient import IMAPClient

CODE = re.compile(rb"\b(\d{6})\b")

with IMAPClient("mail.resifactory.net", port=993, ssl=True) as c:
    c.login(os.environ["OMS_USER"], os.environ["OMS_APP_PASS"])
    c.select_folder("INBOX")

    # Anything already waiting.
    for uid, data in c.fetch(c.search(["TO", "mailbox042@example.com"]), ["RFC822"]).items():
        if m := CODE.search(data[b"RFC822"]):
            print("code:", m.group(1).decode()); raise SystemExit

    # Then wait to be pushed new mail — no polling.
    c.idle()
    while True:
        for _ in c.idle_check(timeout=30):
            c.idle_done()
            uids = c.search(["TO", "mailbox042@example.com"])
            for uid, data in c.fetch(uids, ["RFC822"]).items():
                if m := CODE.search(data[b"RFC822"]):
                    print("code:", m.group(1).decode()); raise SystemExit
            c.idle()
```

### Narrowing to one mailbox

Because every mailbox shares one `INBOX`, `TO` is how you pick one out:

```python
uids = m.uid("SEARCH", None, 'TO', '"mailbox042@example.com"')
```

Supported search terms are listed in [What the server supports](/imap/capabilities.md). Anything unsupported comes back as `NO [CANNOT]` rather than silently matching everything — so if a search fails, rewrite it rather than trusting a wide result.

## Node.js

Using [`imapflow`](https://www.npmjs.com/package/imapflow):

```javascript
import { ImapFlow } from 'imapflow';

const client = new ImapFlow({
  host: 'mail.resifactory.net',
  port: 993,
  secure: true,                       // implicit TLS, not STARTTLS
  auth: { user: process.env.OMS_USER, pass: process.env.OMS_APP_PASS },
});

await client.connect();
const lock = await client.getMailboxLock('INBOX');

try {
  // Everything currently visible to this credential.
  for await (const msg of client.fetch('1:*', { envelope: true, uid: true })) {
    console.log(msg.uid, msg.envelope.to?.[0]?.address, msg.envelope.subject);
  }

  // Push notification on new mail — no polling loop.
  client.on('exists', async (data) => {
    const msg = await client.fetchOne(String(data.count), { source: true });
    console.log('new mail:', msg.envelope?.subject);
  });
} finally {
  lock.release();
}
```

`imapflow` keeps the connection in IDLE for you when it is otherwise idle, so you get pushed mail without writing a polling loop.

## Go

Using [`go-imap/v2`](https://github.com/emersion/go-imap):

```go
package main

import (
	"crypto/tls"
	"log"
	"os"

	"github.com/emersion/go-imap/v2"
	"github.com/emersion/go-imap/v2/imapclient"
)

func main() {
	c, err := imapclient.DialTLS("mail.resifactory.net:993", &imapclient.Options{
		TLSConfig: &tls.Config{ServerName: "mail.resifactory.net"},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer c.Close()

	if err := c.Login(os.Getenv("OMS_USER"), os.Getenv("OMS_APP_PASS")).Wait(); err != nil {
		log.Fatal(err)
	}

	box, err := c.Select("INBOX", nil).Wait()
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("%d messages visible to this credential", box.NumMessages)

	criteria := &imap.SearchCriteria{
		Header: []imap.SearchCriteriaHeaderField{{Key: "To", Value: "mailbox042@example.com"}},
	}
	res, err := c.UIDSearch(criteria, nil).Wait()
	if err != nil {
		log.Fatal(err) // includes NO [CANNOT] for unsupported criteria
	}
	log.Printf("matched %v", res.AllUIDs())
}
```

## Practical notes

### Let the window do the filtering

If your code only cares about recent mail, set a narrow [inbox window](/imap/inbox-window.md) on the app password rather than filtering by date in code. The server then never sends you the older mail at all, which is faster than receiving it and discarding it.

### Handle UIDVALIDITY changes

`UIDVALIDITY` changes when the set of mailboxes your credential can see changes — for example when you buy more. If it differs from what you stored, discard your cached UIDs and resync. Every mature IMAP library exposes this; do not ignore it.

### Do not hammer the server

* Use `IDLE`. New mail is pushed to you as it is stored.
* If you cannot use IDLE, use `NOOP` on a sensible interval — it reports mailbox growth. Polling every few hundred milliseconds gains you nothing.
* Keep one connection per credential. Opening several does not make mail arrive sooner.
* Back off after an authentication failure. Repeated failures temporarily block further attempts from that username and IP.

### Keep the credential out of your source

Read it from the environment or a secrets manager. Give each script its own app password so you can revoke one without touching the rest. See [Keeping credentials safe](/credentials/security.md).

### There is no SMTP

Nothing here sends mail. If your code needs to send, use a separate service.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.resifactory.net/imap/clients/custom-code.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
